﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using MacomberMap.Common.Database_Interface.CIM;
using MacomberMap.Common.Database_Interface.CIM.One_Line_Interface;
using MacomberMap.Common.Database_Interface.CIM.Components;
using System.IO;
using System.Runtime.InteropServices;
using MacomberMap.Common.Internals;
using MacomberMap.Common.User_Interfaces.Components;
using MacomberMap.Common.User_Interfaces.One_Lines.Elements;
using MacomberMap.Common.Components;
using MacomberMap.Common.Database_Interface;
using MacomberMap.Common.Database_Interface.CIM.User_Interfaces;
using MacomberMap.Common.User_Interfaces.One_Lines;
using MacomberMap.Common.Logging;
using MacomberMap.Common.User_Interfaces.Events;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Reflection;
using MacomberMap.Common.Types;
using System.Diagnostics;
using ICSharpCode.SharpZipLib.Zip;
using System.Threading;
using MacomberMap.Common.Database_Interface.CIM.CIM_Interface;
using com.tomsawyer.drawing;
using com.tomsawyer.licensing;
using com.tomsawyer.service.layout.xml;
using com.tomsawyer.service;
using com.tomsawyer.service.layout.client;
using com.tomsawyer.service.layout;
using System.Data.Common;

namespace MacomberMap.Common.Database_Interface.CIM.One_Line_Interface
{
    /// <summary>
    /// This class displays a one-line editor
    /// </summary>
    public partial class frmOneLineEditor : Form
    {
        #region Variable declarations
        /// <summary>The path for auto-saved files</summary>
        private string AutoSaveDirectory = null;

        /// <summary>The path for failed imports files</summary>
        private string FailedImportDirectory = null;

        /// <summary>The CIM database</summary>
        public MM_Database_CIM CIM;

        /// <summary>The repository database</summary>
        public MM_Database_Oracle RepositoryDb;

        /// <summary>The loading percentage label</summary>
        public ToolStripStatusLabel lblStat;

        /// <summary>The path to the rgb.txt file</summary>
        private string RGB;

        /// <summary>The path to the eset</summary>
        private XmlDocument ESet;

        /// <summary>The tuning parameters for one-line import</summary>
        private List<MM_OneLine_TuningParameter> TuningParameters = null;

        /// <summary>The display parameters for the one-line editor</summary>
        public MM_OneLine_Display DisplayParams;

        /// <summary>The first of two controls for which distance and angle will be calculated</summary>
        private Control Ctl1 = null;

        /// <summary>The last opened DDL</summary>
        private String LastDDL = "";

        /// <summary>Our collection of CIM controls</summary>
        private Dictionary<MM_Element, Control> CIMControls = new Dictionary<MM_Element, Control>();

        /// <summary>The elements retrieved from the one-lines</summary>
        private Dictionary<MM_OneLine_Element, MM_OneLine_XmlComponent> Elements;

        /// <summary>The CIM element for the substation</summary>
        private CIM_Element SubElement;

        /// <summary>Our collection of one-line XML components</summary>
        private Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents;

        /// <summary>The connection of linkages between elements and adjacent elements</summary>
        private Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> Linkages;

        /// <summary>The menu for handling right-click handling</summary>
        private ContextMenuStrip RightClickMenu = new ContextMenuStrip();

        /// <summary>A data table of display elements</summary>
        private DataTable DisplayElements;

        /// <summary>The connection to our new database</summary>
        private DbConnection NewDb;
        #endregion

        #region Initalization
        /// <summary>
        /// Initialize a new one-line editor
        /// </summary>
        public frmOneLineEditor(MM_Database_Oracle RepositoryDb, MM_Database_CIM CIM)
        {
            this.CIM = CIM;
            this.RepositoryDb = RepositoryDb;
            InitializeComponent();
            lvElements.FullRowSelect = true;
            olView.AssignConnection(CIM);
            olView.LastCIM = Path.GetFileNameWithoutExtension(MM_Database_CIM.CIMFileName);
            this.DisplayParams = olView.DisplayParameters;
            DisplayParams.ElementsAdded += olView_ElementAdded;
            DisplayParams.ElementsAssigned += olView_ElementAssigned;
            DisplayParams.ElementsSelected += olView_ElementSelected;
            DisplayParams.ElementsRemoved += olView_ElementRemoved;
            DisplayParams.NodesAdded += olView_NodesAdded;
            DisplayParams.NodesRemoved += olView_NodesRemoved;
            DisplayParams.NodesSelected += olView_NodesSelected;
            ssMain.Items.Add(lblStat = new ToolStripStatusLabel("Loading " + this.CIM.Name));
            lblStat.ForeColor = Color.White;
            lblStat.TextImageRelation = TextImageRelation.Overlay;
            lblStat.ImageScaling = ToolStripItemImageScaling.None;
            tmrMain.Enabled = true;
            evLog.AssignConnectors(RepositoryDb, CIM);
            evLog.AssignEventHandlers(MM_Event_Viewer.IncludeAllEvents);
            DisplayElements = new DataTable("DisplayElements");
            DisplayElements.PrimaryKey = new DataColumn[] { DisplayElements.Columns.Add("Element", typeof(MM_OneLine_Linked)) };
            DisplayElements.Columns.Add("Name", typeof(String));
            DataView dv = new DataView(DisplayElements);
            dv.Sort = "Name";
            cmbElements.DisplayMember = "Name";
            cmbElements.ValueMember = "Element";
            cmbElements.DataSource = dv;
            DisplayParams.InformationMode = true;
            this.DoubleBuffered = true;
        }
        #endregion

        #region Timer update
        /// <summary>
        /// Every second, update the critical information
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tmrMain_Tick(object sender, EventArgs e)
        {
            if (MM_Database_CIM.CIMLoader.Completed && lblStat != null)
            {
                ssMain.Items.Remove(lblStat);
                lblStat.Dispose();
                lblStat = null;
                this.Cursor = Cursors.Default;
            }
            else if (lblStat != null)
            {
                float Percentage = (float)MM_Database_CIM.CIMLoader.Position / (float)MM_Database_CIM.CIMLoader.FileSize;
                lblStat.Text = "Loading " + System.IO.Path.GetFileNameWithoutExtension(MM_Database_CIM.CIMFileName) + ": " + Percentage.ToString("0%");

                if (lblStat.Image == null)
                    lblStat.Image = new Bitmap(lblStat.Width, lblStat.Height);
                RectangleF OutRect = new RectangleF(0, 0, (float)lblStat.Width * Percentage, (float)lblStat.Height);
                using (Graphics g = Graphics.FromImage(lblStat.Image))
                using (Brush NewBrush = new System.Drawing.Drawing2D.LinearGradientBrush(OutRect, Color.DarkGreen, Color.LightGreen, 45f))
                    g.FillRectangle(NewBrush, OutRect);

                lblStat.Invalidate();
            }
        }




        #endregion

        #region Element selection
        /// <summary>
        /// Handle the element removal 
        /// </summary>
        /// <param name="Element"></param>
        private void olView_ElementRemoved(MM_OneLine_Element[] Element)
        {
            foreach (MM_OneLine_Element Elem in Element)
                if (Elem != null)
                {
                    if (DisplayElements.Rows.Find(Elem) != null)
                        DisplayElements.Rows.Find(Elem).Delete();
                    Elem.MouseDown -= Control_MouseDown;
                    Elem.MouseUp -= Control_MouseUp;
                    if (Elem.Tag is MM_OneLine_XmlComponent)
                        OLComponents.Remove(Elem.Tag as MM_OneLine_XmlComponent);

                    CIMListViewItem lv = lvElements.Items[Elem.TEID.ToString()] as CIMListViewItem;
                    if (lv != null)
                        lv.UpdateState();
                }
            UpdateCIMElements();
            pnlZoomOut.Invalidate();
        }

        /// <summary>
        /// Handle the node removal
        /// </summary>
        /// <param name="Nodes"></param>
        private void olView_NodesRemoved(MM_OneLine_Node[] Nodes)
        {
            foreach (MM_OneLine_Node Node in Nodes)
            {
                DisplayElements.Rows.Find(Node).Delete();
                CIMListViewItem lv = lvElements.Items[Node.TEID.ToString()] as CIMListViewItem;
                if (lv != null)
                    lv.UpdateState();
            }
            pnlZoomOut.Invalidate();
        }

        /// <summary>
        /// Handle node selection
        /// </summary>
        /// <param name="Nodes"></param>
        private void olView_NodesSelected(MM_OneLine_Node[] Nodes)
        {
            if (Nodes.Length == 0)
                cmbElements.SelectedValue = null;
            else
                cmbElements.SelectedValue = Nodes[0];
            pgMain.SelectedObjects = Nodes;
        }


        /// <summary>
        /// Handle element selection
        /// </summary>
        /// <param name="Elements"></param>
        private void olView_ElementSelected(MM_OneLine_Element[] Elements)
        {

            if (Elements.Length == 0)
                cmbElements.SelectedItem = null;
            else
                cmbElements.SelectedValue = Elements[0];
            pgMain.SelectedObjects = Elements;



            /*
            lvElements.SelectedItems.Clear();
            foreach (MM_OneLine_Element Elem in Elements)
                foreach (CIMListViewItem lvI in lvElements.Items)
                    if (lvI.OLElement == Elem)
                    {
                        lvI.EnsureVisible();
                        lvI.Focused = lvI.Selected = true;
                    }
            pnlZoomOut.Invalidate();*/
        }

        /// <summary>
        /// Handle element assignment
        /// </summary>
        /// <param name="Elements"></param>
        public void olView_ElementAssigned(MM_OneLine_Element[] Elements)
        {
            foreach (MM_OneLine_Element Element in Elements)
                if (Element.BaseElement != null)
                {
                    CIMListViewItem lv = lvElements.Items[Element.BaseElement.TEID.ToString()] as CIMListViewItem;
                    if (lv != null)
                    {
                        lv.UpdateState();
                        if (lv.MMElement is MM_Node && (lv.MMElement as MM_Node).BusbarSection != null)
                            (lvElements.Items[(lv.MMElement as MM_Node).BusbarSection.TEID.ToString()] as CIMListViewItem).UpdateState();
                        if (Element.Tag is MM_OneLine_XmlComponent)
                            (Element.Tag as MM_OneLine_XmlComponent).AssociatedElement = lv.CIMElement;
                    }
                    if (Element.Descriptor != null)
                        Element.Descriptor.RecomputeSize();
                }
            pnlZoomOut.Invalidate();
        }


        /// <summary>
        /// Handle element addition
        /// </summary>
        /// <param name="Elements"></param>
        public void olView_ElementAdded(MM_OneLine_Element[] Elements)
        {
            /*try
            {*/
                foreach (MM_OneLine_Element Elem in Elements)
                {
                    Elem.MouseDown -= Control_MouseDown;
                    Elem.MouseUp -= Control_MouseUp;

                    Elem.MouseDown += Control_MouseDown;
                    Elem.MouseUp += Control_MouseUp;

                    //Make sure the CIM element is updated for the xml component.
                    if (Elem.Tag is MM_OneLine_XmlComponent)
                        if (Elem.BaseElement == null)
                            (Elem.Tag as MM_OneLine_XmlComponent).AssociatedElement = null;
                        else
                            (Elem.Tag as MM_OneLine_XmlComponent).AssociatedElement = MM_Database_CIM.TEIDs[Elem.BaseElement.TEID];

                    //Ensure the rdf:IDs are appropriately traced
                    if (Elem is MM_OneLine_Transformer)
                        foreach (MM_OneLine_TransformerWinding Winding in (Elem as MM_OneLine_Transformer).Windings)
                            if (Winding != null && Winding.BaseElement != null && lvElements.Items.ContainsKey(Winding.BaseElement.TEID.ToString()))
                            {
                                CIMListViewItem LV = lvElements.Items[Winding.BaseElement.TEID.ToString()] as CIMListViewItem;
                                Winding.rdfID = LV.CIMElement.rdfID;
                                LV.MMElement = Winding.BaseElement;
                            }



                    if (Elem.BaseElement != null && lvElements.Items.ContainsKey(Elem.TEID.ToString()))
                    {
                        CIMListViewItem lv = lvElements.Items[Elem.TEID.ToString()] as CIMListViewItem;
                        Elem.rdfID = lv.CIMElement.rdfID;
                        if (Elem.Descriptor != null)
                            Elem.Descriptor.RecomputeSize();
                        if (Elem is MM_OneLine_Breaker)
                            (Elem as MM_OneLine_Breaker).Opened = (Elem as MM_OneLine_Breaker).ScadaOpened = (bool.Parse(lv.CIMElement["cim:Switch.normalOpen"]) ? CheckState.Checked : CheckState.Unchecked);
                        else if (Elem is MM_OneLine_Switch)
                            (Elem as MM_OneLine_Switch).Opened = (Elem as MM_OneLine_Switch).ScadaOpened = (bool.Parse(lv.CIMElement["cim:Switch.normalOpen"]) ? CheckState.Checked : CheckState.Unchecked);
                        else if (Elem is MM_OneLine_Node)
                        {
                            if (!String.IsNullOrEmpty((Elem as MM_OneLine_Node).ResourceNode = lv.CIMElement["etx:ElectricalBus>etx:ResourceNode>Name"]) && Elem.SecondaryDescriptor == null)
                                DisplayParams.HandleAddedElements(new MM_OneLine_Element[] { Elem.SecondaryDescriptor = new MM_OneLine_SecondaryDescriptor(Elem, DisplayParams) });
                        }

                    }

                    /*
                    if (Elem.BaseElement != null && lvElements.Items.ContainsKey(Elem.TEID.ToString()))
                    {                    
                        lvElements.Items[Elem.TEID.ToString()].ForeColor = Color.White;
                        Elem.rdfID = (lvElements.Items[Elem.TEID.ToString()].Tag as CIM_Element).rdfID.ToString();
                    }
                    if (Elem is MM_OneLine_Transformer)
                        foreach (Object obj in (Elem as MM_OneLine_Transformer).Windings)
                            if (obj is MM_OneLine_TransformerWinding && (obj as MM_OneLine_TransformerWinding).Winding != null)
                                lvElements.Items[(obj as MM_OneLine_TransformerWinding).Winding.TEID.ToString()].ForeColor = Color.White;
                    */
                    if (DisplayElements.Rows.Find(Elem) == null)
                        if (Elem.BaseElement != null)
                            DisplayElements.Rows.Add(Elem, Elem.BaseElement.ElemType.Name + " " + Elem.BaseElement.Name);
                        else
                            DisplayElements.Rows.Add(Elem, Elem.ToString());
                }
                UpdateCIMElements();
                pnlZoomOut.Invalidate();
            /*}
            catch (Exception ex)
            {
                MessageBox.Show("Error when handling added elements: " + ex.Message + "\n" + ex.StackTrace, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }*/
        }

        /// <summary>
        /// Handle node addition
        /// </summary>
        /// <param name="Nodes"></param>
        private void olView_NodesAdded(MM_OneLine_Node[] Nodes)
        {
            foreach (MM_OneLine_Node Node in Nodes)
                if (Node != null)
                {
                    CIMListViewItem FoundItem = lvElements.Items[Node.BaseElement.TEID.ToString()] as CIMListViewItem;
                    if (FoundItem != null)
                    {
                        FoundItem.UpdateState();
                        Node.rdfID = FoundItem.CIMElement.rdfID;
                    }
                    else
                    { }

                    foreach (KeyValuePair<MM_OneLine_Element, MM_OneLine_PokePoint[]> Pokes in Node.ConnectionPoints)
                        foreach (MM_OneLine_PokePoint Poke in Pokes.Value)
                            if (Poke is MM_OneLine_PricingVector && lvElements.Items.ContainsKey(Poke.TEID.ToString()))
                            {
                                FoundItem = lvElements.Items[Poke.TEID.ToString()] as CIMListViewItem;
                                FoundItem.UpdateState();
                                Poke.rdfID = FoundItem.CIMElement.rdfID;
                            }



                    if (DisplayElements.Rows.Find(Node) == null)
                        DisplayElements.Rows.Add(Node, Node.BaseElement.ElemType.Name + " " + Node.BaseElement.Name);
                }
            pnlZoomOut.Invalidate();
        }
        #endregion
        #region Menu commands

        /// <summary>
        /// Assign a base element to the one-line viewer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void assignBaseElementToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (frmSubstationChooser Chooser = new frmSubstationChooser())
                if (Chooser.ShowDialog() == DialogResult.OK)
                    AssignBaseElement(Chooser.SelectedElement);
        }


        /// <summary>
        /// Import a WebFG DDL
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void importWebFGDDLToolStripMenuItem_Click(object sender, EventArgs e)
        {

            if (String.IsNullOrEmpty(RGB))
                using (OpenFileDialog oFd = new OpenFileDialog())
                {
                    oFd.Title = "Open the EMS RGB color specifications...";
                    oFd.Filter = "rgb.txt|rgb.txt";
                    if (oFd.ShowDialog() == DialogResult.OK)
                        RGB = oFd.FileName;
                    else
                        return;
                }

            if (ESet == null)
                using (OpenFileDialog oFd = new OpenFileDialog())
                {
                    oFd.Title = "Open the ERCOT WebFG element set...";
                    oFd.Filter = "*_eset.ddl|*_eset.ddl";
                    if (oFd.ShowDialog() == DialogResult.OK)
                        ESet = MM_OneLine_Parser.ReadDDL(oFd.FileName);
                    else
                        return;
                }

            //Now, import the DDL
            using (OpenFileDialog oFd = new OpenFileDialog())
            {
                oFd.Title = "Open an ERCOT Substation one-line";
                oFd.Filter = "ZNET_*_dset.ddl|ZNET_*_dset.ddl";
                if (oFd.ShowDialog() == DialogResult.OK)
                    LoadOneLineDDL(oFd.FileName);
                else
                    return;
            }
        }

        /// <summary>
        /// Add pricing vectors to the display
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void addPricingVectorsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (addPricingVectorsToolStripMenuItem.Checked)
                MessageBox.Show("Pricing vectors have already been added!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            else
            {
                MM_DDL_Importer.BuildPricingVectors(OLComponents, olView);
                UpdateControls();
                addPricingVectorsToolStripMenuItem.Checked = true;
            }
        }

        /// <summary>
        /// Match elements to their one line components
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void matchElementsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (matchElementsToolStripMenuItem.Checked)
                MessageBox.Show("Elements have already been matched up!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            else
            {
                //First, make sure all elements that should be matched up are

                MM_DDL_Importer.MatchElements(OLComponents, TuningParameters, olView, Linkages);
                UpdateControls();
                AssignBaseElement(SubElement);
                olView.Refresh();
                matchElementsToolStripMenuItem.Checked = true;
            }
        }

        /// <summary>
        /// Load a one-line DDL into memory
        /// </summary>
        /// <param name="FileName"></param>
        private void LoadOneLineDDL(String FileName)
        {
            validateTopologyToolStripMenuItem.Checked = approveOneLineToolStripMenuItem.Checked = matchElementsToolStripMenuItem.Checked = rollUpDescriptorsToolStripMenuItem.Checked = removeUnlinkedElementsToolStripMenuItem.Checked = rollUpNodesToolStripMenuItem.Checked = addPricingVectorsToolStripMenuItem.Checked = false;

            if (String.IsNullOrEmpty(FileName))
            {
                importWebFGDDLToolStripMenuItem_Click(this, EventArgs.Empty);
                return;
            }
            olView.SuspendLayout();
            olView.ClearElements();
            olView.PanelScrollPosition = Point.Empty;

            //Load in the tuning parameters
            XmlDocument xTuning = new XmlDocument();
            xTuning.Load(Application.StartupPath + "\\MM_OneLine_TuningParameters.xml");
            TuningParameters = new List<MM_OneLine_TuningParameter>(xTuning.DocumentElement.ChildNodes.Count);
            foreach (XmlElement xTuneParam in xTuning.DocumentElement.ChildNodes)
                TuningParameters.Add(new MM_OneLine_TuningParameter(xTuneParam));



            //Pull in all of our components
            OLComponents = new Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element>();
            XmlDocument xDset = MM_OneLine_Parser.ReadDDL(FileName);
            foreach (XmlElement xLayer in xDset.SelectNodes("//simple_layer"))
                foreach (XmlElement xComponent in xLayer.ChildNodes)
                    OLComponents.Add(new MM_OneLine_XmlComponent(xComponent, ESet), null);

            //Now, roll up our transformer windings into a single transformer component, and re-integrate
            List<MM_OneLine_XmlComponent> XFs = new List<MM_OneLine_XmlComponent>();
            MM_DDL_Importer.BuildTransformers(OLComponents, XFs);
            foreach (MM_OneLine_XmlComponent XF in XFs)
                OLComponents.Add(XF, null);


            Linkages = MM_DDL_Importer.BuildLinkages(OLComponents);


            //Wait until our CIM file is loaded
            while (!MM_Database_CIM.CIMLoader.Completed)
                Application.DoEvents();

            //Now, pull in all trace elements (components within the substation)
            String SubName = Path.GetFileNameWithoutExtension(FileName);
            SubName = SubName.Substring(SubName.IndexOf('_') + 1);
            SubName = SubName.Substring(0, SubName.LastIndexOf('_'));

            CIM_Element BaseElement = MM_Database_CIM.LocateElement(SubName, "Substation", SubName, "");
            if (BaseElement == null)
            {
                //MessageBox.Show("Unable to locate a base element!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            AssignBaseElement(BaseElement);

            //Import all our elements now, and link them together            
            MM_DDL_Importer.ImportElements(OLComponents, olView, this.tTip, TuningParameters, DisplayParams, Linkages, out Elements, SubElement);

            //Now, update our list of element types
            unlinkedToolStripMenuItem.DropDownItems.Clear();
            foreach (MM_OneLine_XmlComponent Component in OLComponents.Keys)
                if (!Component.Processed && !DisplayParams.UnlinkedElements.ContainsKey(Component.Name))
                    DisplayParams.UnlinkedElements.Add(Component.Name, true);

            foreach (KeyValuePair<String, bool> kvp in DisplayParams.UnlinkedElements)
                (unlinkedToolStripMenuItem.DropDownItems.Add(kvp.Key) as ToolStripMenuItem).Checked = kvp.Value;

            //Set our display parameters, and update
            DisplayParams.DDLImportMode = dDLImportModeToolStripMenuItem.Checked = true;
            UpdateControls();
            LastDDL = FileName;





            //Make sure our import button is visible
            importToolStripMenuItem.Visible = true;
            olView.ResumeLayout(true);
        }

        /// <summary>
        /// Assign a base element with traced elements
        /// </summary>
        /// <param name="BaseElement">The base element</param>
        /// <param name="TraceElements">The traced elements</param>
        private void AssignBaseElement(CIM_Element BaseElement, CIM_Element[] TraceElements)
        {
            //Assign our element            
            olView.BaseElement = MM_Database_CIM.LocateElement(BaseElement, olView.AddedElements);
            this.SubElement = MM_DDL_Importer.SubElement = BaseElement;
            if (olView.BaseElement == null)
                this.Text = "Macomber Map One-Line Editor: " + BaseElement.Type.Split(':')[1] + " " + MM_Repository.TitleCase(BaseElement.Name);
            else if (olView.BaseElement is MM_Substation)
                this.Text = "Macomber Map One-Line Editor: " + (olView.BaseElement as MM_Substation).LongName;
            else
                this.Text = "Macomber Map One-Line Editor: " + olView.ContingencyLongName;
            
            //Clear and re-propigate the list view
            lvElements.Items.Clear();
            lvElements.Groups.Clear();
            lvElements.ShowGroups = true;

            //Store our elements in a sorted fashion
            SortedDictionary<String, List<CIMListViewItem>> OutItems = new SortedDictionary<string, List<CIMListViewItem>>();




            SetListViewGroupColor(Color.FromArgb(0x00cccccc));
            foreach (CIM_Element TraceElement in TraceElements)
            {
                if (!String.IsNullOrEmpty(TraceElement.TEID))
                {
                    MM_Element FoundElem = MM_Database_CIM.LocateElement(TraceElement, olView.AddedElements);

                    if (FoundElem != null)
                    {
                        CIMListViewItem lvi = new CIMListViewItem(TraceElement, FoundElem, olView);
                        if (!OutItems.ContainsKey(FoundElem.ElemType.Name))
                            OutItems.Add(FoundElem.ElemType.Name, new List<CIMListViewItem>());
                        OutItems[FoundElem.ElemType.Name].Add(lvi);
                    }
                }
            }

            foreach (KeyValuePair<String, List<CIMListViewItem>> kvp in OutItems)
            {
                kvp.Value.Sort();
                ListViewGroup Group = lvElements.Groups.Add(kvp.Key, kvp.Key);
                foreach (CIMListViewItem lvi in kvp.Value)
                {
                    lvi.Group = Group;
                    lvElements.Items.Add(lvi);
                }
            }
        }

        /// <summary>
        /// Assign a base element to the display
        /// </summary>
        /// <param name="BaseElement"></param>
        private void AssignBaseElement(CIM_Element BaseElement)
        {

            //Assign our element            
            olView.BaseElement = MM_Database_CIM.LocateElement(BaseElement, olView.AddedElements);
            this.SubElement = MM_DDL_Importer.SubElement = BaseElement;
            this.Text = "Macomber Map One-Line Editor: " + this.SubElement.Name;

            //Clear and re-propigate the list view
            lvElements.Items.Clear();
            lvElements.Groups.Clear();
            lvElements.ShowGroups = true;

            //Store our elements in a sorted fashion
            SortedDictionary<String, List<CIMListViewItem>> OutItems = new SortedDictionary<string, List<CIMListViewItem>>();




            SetListViewGroupColor(Color.FromArgb(0x00cccccc));
            foreach (CIM_Element TraceElement in SubElement.Elements)
            {
                if (!String.IsNullOrEmpty(TraceElement.TEID))
                {
                    MM_Element FoundElem = MM_Database_CIM.LocateElement(TraceElement, olView.AddedElements);

                    if (FoundElem != null)
                    {
                        CIMListViewItem lvi = new CIMListViewItem(TraceElement, FoundElem, olView);
                        if (!OutItems.ContainsKey(FoundElem.ElemType.Name))
                            OutItems.Add(FoundElem.ElemType.Name, new List<CIMListViewItem>());
                        OutItems[FoundElem.ElemType.Name].Add(lvi);
                    }
                }
            }

            foreach (KeyValuePair<String, List<CIMListViewItem>> kvp in OutItems)
            {
                kvp.Value.Sort();
                ListViewGroup Group = lvElements.Groups.Add(kvp.Key, kvp.Key);
                foreach (CIMListViewItem lvi in kvp.Value)
                {
                    lvi.Group = Group;
                    lvElements.Items.Add(lvi);
                }
            }
        }



        /// <summary>
        /// Handle the mouse down on a control
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void Control_MouseDown(object sender, MouseEventArgs e)
        {
            Ctl1 = sender as Control;
            pgMain.SelectedObject = sender as Control;
            base.OnMouseDown(e);
        }



        /// <summary>
        /// Handle the mouse up on a control - if we're on another control, show our tooltip
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void Control_MouseUp(object sender, MouseEventArgs e)
        {

            //Pass the right mouse button
            if (e.Button == MouseButtons.Right && (sender as MM_OneLine_Element).BaseElement != null)
            {
                CIMListViewItem lv = lvElements.Items[(sender as MM_OneLine_Element).BaseElement.TEID.ToString()] as CIMListViewItem;
                if (lv != null)
                {
                    lv.EnsureVisible();
                    MM_OneLine_Element Sender = sender as MM_OneLine_Element;
                    ShowPopupMenu(lv, Sender, Sender.PointToScreen(e.Location));
                    return;
                }
            }
            if (sender is MM_OneLine_Descriptor && e.Button == MouseButtons.Right)
                olView_PanelClicked(olView, new MouseEventArgs(e.Button, e.Clicks, e.X + (sender as Control).Left, e.Y + (sender as Control).Top, e.Delta));
            if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
            {
                (sender as Control).SendToBack();
                olView.Refresh();
                return;
            }
            else if (Ctl1 == null || (!DisplayParams.InformationMode && (Control.ModifierKeys & Keys.Alt) != Keys.Alt))
                return;
            Point CurrentPoint = new Point(Ctl1.Left + e.X, Ctl1.Top + e.Y);

            foreach (Control Ctl2 in olView.PanelControls)
                if (Ctl2.Visible && Ctl2.Bounds.Contains(CurrentPoint))
                {
                    if (Ctl2 == Ctl1)
                    {
                        StringBuilder OutString = new StringBuilder();
                        OutString.AppendLine(sender.ToString());
                        if (Ctl2.Tag is MM_OneLine_XmlComponent)
                            OutString.AppendLine((Ctl2.Tag as MM_OneLine_XmlComponent).ToString());
                        MM_OneLine_XmlComponent BaseComponent = Ctl1.Tag as MM_OneLine_XmlComponent;
                        if (Ctl2 is MM_OneLine_PokePoint)
                        {
                            OutString.AppendLine("Connected elements: ");
                            foreach (MM_OneLine_Node Node in olView.DisplayNodes.Values)
                                foreach (KeyValuePair<MM_OneLine_Element, MM_OneLine_PokePoint[]> kvp in Node.ConnectionPoints)
                                    if (Array.IndexOf(kvp.Value, Ctl2) != -1)
                                        OutString.AppendLine(String.Format("  Node {0} ({1}), to {2} {3} ({4},{5})-{6},{7}", Node.BaseElement.Name, Node.BaseElement.TEID.ToString("#,##0"), kvp.Key.BaseElement.ElemType.Name, kvp.Key.BaseElement.Name, kvp.Key.Left, kvp.Key.Top, kvp.Key.Right, kvp.Key.Bottom));

                            OutString.AppendLine();
                            OutString.AppendLine((Ctl1 as MM_OneLine_PokePoint).ToString() + " information:");
                            foreach (KeyValuePair<MM_OneLine_Element, MM_OneLine_PokePoint[]> kvp in (Ctl1 as MM_OneLine_PokePoint).BaseNode.ConnectionPoints)
                            {
                                OutString.AppendLine("  " + kvp.Key.ToString());
                                foreach (MM_OneLine_PokePoint Poke in kvp.Value)
                                    OutString.AppendLine("    " + Poke.ToString());
                            }
                        }
                        else if (BaseComponent != null)
                        {
                            if (BaseComponent.AssociatedElement != null && BaseComponent.AssociatedElement.Type == "cim:ConnectivityNode")
                            {
                                OutString.AppendLine();
                                OutString.AppendLine("Elements:");
                                foreach (CIM_Element Elem in BaseComponent.AssociatedElement.Elements)
                                    OutString.AppendLine("  " + Elem.ToString());
                            }


                            if (BaseComponent.SubElements != null)
                            {
                                OutString.AppendLine();
                                OutString.AppendLine("Subcomponents:");
                                foreach (MM_OneLine_XmlComponent Component in BaseComponent.SubElements)
                                {
                                    OutString.AppendLine("  " + Component.ToString());
                                    if (BaseComponent.Name.StartsWith("TRANS_") && Component.SubElements != null)
                                        foreach (MM_OneLine_XmlComponent SubComponent in Component.SubElements)
                                            OutString.AppendLine("    " + SubComponent.ToString());
                                }
                            }

                            if (Linkages.ContainsKey(BaseComponent))
                            {
                                OutString.AppendLine();
                                OutString.AppendLine("Linkages:");
                                foreach (MM_OneLine_XmlComponent Component in Linkages[BaseComponent])
                                    OutString.AppendLine("  " + Component.ToString());
                            }

                            Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> OutList = new Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]>();
                            OutString.AppendLine("");
                            try
                            {
                                MM_DDL_Importer.ConnectComponents(Linkages, (Ctl1 as MM_OneLine_Element).Tag as MM_OneLine_XmlComponent, OutList, new MM_OneLine_XmlComponent[0]);
                                if (Ctl1 is MM_OneLine_PokePoint)
                                {
                                    OutString.AppendLine("Connected elements: ");
                                    foreach (KeyValuePair<MM_OneLine_Element, MM_OneLine_PokePoint[]> kvp in (Ctl1 as MM_OneLine_PokePoint).BaseNode.ConnectionPoints)
                                    {
                                        OutString.AppendFormat("  {0}: ({1},{2}-{3},{4})\n", kvp.Key, kvp.Key.Left, kvp.Key.Top, kvp.Key.Right, kvp.Key.Bottom);
                                        for (int a = 0; a < kvp.Value.Length; a++)
                                            OutString.AppendFormat("     {0}{2}, {3}{1}-{0}{4}, {5}{1}\n", (kvp.Value[a].IsJumper ? "[" : kvp.Value[a].IsVisible ? "(" : "{"), (kvp.Value[a].IsJumper ? "]" : kvp.Value[a].IsVisible ? ")" : "}"), kvp.Value[a].Left, kvp.Value[a].Top, kvp.Value[a].Right, kvp.Value[a].Bottom);

                                        OutString.AppendLine();
                                    }
                                }
                                else
                                {
                                    OutString.AppendLine("Linked elements:");
                                    foreach (KeyValuePair<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> kvp in OutList)
                                    {
                                        OutString.AppendLine("  " + kvp.Key.ToString());
                                        for (int a = kvp.Value.Length - 1; a >= 0; a--)
                                            if (kvp.Value[a] != BaseComponent)
                                                OutString.AppendLine("     via " + kvp.Value[a].ToString());

                                        OutString.AppendLine();
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                OutString.AppendLine(ex.Message);
                            }
                        }
                        Clipboard.Clear();
                        Clipboard.SetText(OutString.ToString());
                        MM_OneLine_Element Elem2 = Ctl1 as MM_OneLine_Element;
                        if ((Elem2 is MM_OneLine_Node && !String.IsNullOrEmpty((Elem2 as MM_OneLine_Node).ResourceNode) && Elem2.SecondaryDescriptor == null) || ((Elem2 is MM_OneLine_PokePoint && !String.IsNullOrEmpty((Elem2 as MM_OneLine_PokePoint).BaseNode.ResourceNode) && Elem2.SecondaryDescriptor == null)))
                        {
                            OutString.AppendLine();
                            OutString.AppendLine("The node associated with this element has a resource node name, with no descriptor. Would you like to add one?");
                            if (MessageBox.Show(OutString.ToString(), Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                            {
                                MM_OneLine_SecondaryDescriptor NewDesc = new MM_OneLine_SecondaryDescriptor(Elem2, DisplayParams);
                                Elem2.SecondaryDescriptor = NewDesc;
                                Elem2.ForeColor = NewDesc.ForeColor = Color.Gray;
                                NewDesc.RecomputeSize();
                                NewDesc.Location = new Point(Elem2.Right, Elem2.Top + (Elem2.Height / 2) - (NewDesc.Height / 2));
                                DisplayParams.HandleAddedElements(new MM_OneLine_Element[] { NewDesc });
                            }
                        }
                        else if (Elem2.BaseElement != null && Elem2.Descriptor == null)
                        {
                            OutString.AppendLine();
                            OutString.AppendLine("This element is data-bound, but doesn't have a descriptor. Would you like to add one?");
                            if (MessageBox.Show(OutString.ToString(), Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                            {
                                MM_OneLine_Descriptor NewDesc = new MM_OneLine_Descriptor(Elem2, DisplayParams);
                                Elem2.Descriptor = NewDesc;
                                Elem2.ForeColor = NewDesc.ForeColor = Color.Gray;
                                NewDesc.Location = new Point(Elem2.Right, Elem2.Top + (Elem2.Height / 2) - (NewDesc.Height / 2));
                                DisplayParams.HandleAddedElements(new MM_OneLine_Element[] { NewDesc });
                            }
                        }
                        else
                            MessageBox.Show(OutString.ToString(), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);

                    }

                    else
                    {
                        if (Ctl1.Tag == null || Ctl2.Tag == null)
                            return;
                        StringBuilder OutStr = new StringBuilder();
                        OutStr.AppendLine("Control 1:");
                        OutStr.AppendLine(Ctl1.Tag.ToString());
                        if (Ctl1.Tag != null && !String.IsNullOrEmpty((Ctl1.Tag as MM_OneLine_XmlComponent).Key))
                            OutStr.AppendLine(" (" + (Ctl1.Tag as MM_OneLine_XmlComponent).Key + ")");
                        OutStr.AppendLine();
                        OutStr.AppendLine();
                        OutStr.AppendLine("Control 2:");
                        if (Ctl2.Tag != null)
                            OutStr.AppendLine(Ctl2.Tag.ToString());
                        if (Ctl2.Tag != null && !String.IsNullOrEmpty((Ctl2.Tag as MM_OneLine_XmlComponent).Key))
                            OutStr.AppendLine(" (" + (Ctl2.Tag as MM_OneLine_XmlComponent).Key + ")");
                        if (Ctl1.Tag != null && !String.IsNullOrEmpty((Ctl1.Tag as MM_OneLine_XmlComponent).Key))
                            OutStr.AppendLine(" (" + (Ctl1.Tag as MM_OneLine_XmlComponent).Key + ")");
                        OutStr.AppendLine();
                        OutStr.AppendLine();
                        double Dist, Angle, DeltaX, DeltaY;
                        bool WithinX, WithinY;
                        MM_OneLine_TuningParameter.ComputeDistanceAndAngle((Ctl1.Tag as MM_OneLine_XmlComponent).ElementRectangle, (Ctl2.Tag as MM_OneLine_XmlComponent).ElementRectangle, out Dist, out Angle, out DeltaX, out DeltaY, out WithinX, out WithinY);
                        OutStr.AppendLine("Within X: " + WithinX.ToString());
                        OutStr.AppendLine("Within Y: " + WithinY.ToString());
                        OutStr.AppendLine("Delta X: " + DeltaX.ToString());
                        OutStr.AppendLine("Delta Y: " + DeltaY.ToString());
                        OutStr.AppendLine("Distance: " + Dist.ToString());
                        OutStr.AppendLine("Angle: " + Angle.ToString());
                        if (Ctl2.Bounds.Contains(Ctl1.Bounds))
                            OutStr.AppendLine("Control 1 within Control 2's bounds.");
                        OutStr.AppendLine();
                        OutStr.AppendLine("Applicable tuning parameters:");

                        foreach (MM_OneLine_TuningParameter Param in TuningParameters)
                            if (Param.Check(Ctl2.Tag as MM_OneLine_XmlComponent, Ctl1.Tag as MM_OneLine_XmlComponent))
                                OutStr.AppendLine(Param.ToString());

                        if (Ctl1.Tag is MM_OneLine_XmlComponent && Ctl2.Tag is MM_OneLine_XmlComponent)
                        {
                            OutStr.AppendLine();
                            MM_OneLine_XmlComponent Target = Ctl1.Tag as MM_OneLine_XmlComponent;
                            if (Ctl1 is MM_OneLine_Transformer)
                            {

                                //If we're a phase shifter, handle accordingly.
                                MM_OneLine_Transformer XF = Ctl1 as MM_OneLine_Transformer;
                                if (XF.Windings.Length >= 2 && XF.Windings[1].IsPhaseShifter)
                                    OutStr.AppendLine("Control 1 is a Phase-shifting transformer.");
                                else
                                    OutStr.AppendLine("Control 1 is a Transformer.");

                                if ((Ctl1.Tag as MM_OneLine_XmlComponent).SubElements.Count < 2)
                                    MessageBox.Show(OutStr.ToString(), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                                else
                                {
                                    OutStr.AppendLine("Would you like to roll up the control");
                                    OutStr.AppendLine("  " + Ctl2.Tag.ToString());
                                    OutStr.AppendLine("into the first winding");
                                    OutStr.AppendLine("  " + (Ctl1.Tag as MM_OneLine_XmlComponent).SubElements[0].ToString() + " (respond yes),");
                                    OutStr.AppendLine("or into the second winding");
                                    OutStr.AppendLine("  " + (Ctl1.Tag as MM_OneLine_XmlComponent).SubElements[1].ToString() + " (respond no)");
                                    DialogResult Resp = MessageBox.Show(OutStr.ToString(), Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
                                    if (Resp == DialogResult.Yes)
                                        Target = Target.SubElements[0];
                                    else if (Resp == DialogResult.No)
                                        Target = Target.SubElements[1];
                                    else
                                        return;
                                }
                            }
                            else
                            {
                                OutStr.AppendLine("Would you like to roll up Control 2 into " + Ctl1.GetType().Name.Substring(Ctl1.GetType().Name.LastIndexOf('_') + 1) + " 1 ?");
                                if (MessageBox.Show(OutStr.ToString(), Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
                                    return;
                            }
                            if (Target.SubElements == null)
                                Target.SubElements = new List<MM_OneLine_XmlComponent>(10);
                            Target.SubElements.Add(Ctl2.Tag as MM_OneLine_XmlComponent);
                            (Ctl2.Tag as MM_OneLine_XmlComponent).Parent = Target;
                            int MatchCount = 0;
                            List<MM_OneLine_Element> AssignedElements = new List<MM_OneLine_Element>();
                            MM_DDL_Importer.MatchElement(Ctl1.Tag as MM_OneLine_XmlComponent, Ctl1 as MM_OneLine_Element, ref MatchCount, olView, OLComponents, TuningParameters, AssignedElements);
                            DisplayParams.HandleAssignedElements(AssignedElements.ToArray());
                            Ctl1.Refresh();
                            Ctl2.Refresh();

                            return;
                        }

                        else
                            MessageBox.Show(OutStr.ToString(), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);

                    }
                    return;
                }
        }



        /// <summary>
        /// Display the CIM browser
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cIMBrowserToolStripMenuItem_Click(object sender, EventArgs e)
        {
            new frmCIMBrowser().Show();
        }


        /// <summary>
        /// Handle the DDL Import mode mail menu item toggling
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void dDLImportModeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            dDLImportModeToolStripMenuItem.Checked = (DisplayParams.DDLImportMode ^= true);
            olView.Refresh();
        }

        /// <summary>
        /// Show/hide components
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ComponentsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ComponentsToolStripMenuItem.Checked = (DisplayParams.ShowLinkedComponents ^= true);
            UpdateControls();
        }


        /// <summary>
        /// Show/hide descriptive components
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void descriptivesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            descriptivesToolStripMenuItem.Checked = (DisplayParams.ShowDescriptives ^= true);
            UpdateControls();
        }


        private void connectivityNodesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            connectivityNodesToolStripMenuItem.Checked = (DisplayParams.ShowConnectivityNodes ^= true);
            UpdateControls();
        }

        private void nodeToElementLinesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            nodeToElementLinesToolStripMenuItem.Checked = (DisplayParams.ShowNodeToElementLines ^= true);
            UpdateControls();
        }


        //Go through all components, and update accordingly
        private void UpdateControls()
        {
            SetListViewGroupColor(Color.FromArgb(0x00cccccc));
            CIMControls.Clear();
            foreach (Control ctl in olView.PanelControls)
            {

                if (ctl is MM_OneLine_PokePoint && ctl is MM_OneLine_PricingVector == false && (ctl as MM_OneLine_PokePoint).BaseNode is MM_OneLine_Node)
                {
                    if (ctl.Visible = DisplayParams.ShowConnectivityNodes)
                        ctl.BringToFront();
                }
                else if (ctl is MM_OneLine_Descriptor)
                    ctl.Visible = DisplayParams.ShowDescriptives;

                else if (ctl is MM_OneLine_Element)
                    if (DisplayParams.ShowLinkedComponents && ((ctl as MM_OneLine_Element).BaseElement != null))
                    {
                        ctl.Visible = true;
                        ctl.BringToFront();
                        if (!CIMControls.ContainsKey((ctl as MM_OneLine_Element).BaseElement))
                            CIMControls.Add((ctl as MM_OneLine_Element).BaseElement, ctl);
                    }
                    else if (DisplayParams.ShowUnlinkedComponents && ((ctl as MM_OneLine_Element).BaseElement == null))
                    {
                        String ItemName = (ctl.Tag as MM_OneLine_XmlComponent).Name;
                        if (DisplayParams.UnlinkedElements.ContainsKey(ItemName))
                            ctl.Visible = DisplayParams.UnlinkedElements[ItemName];
                        else
                            ctl.Visible = DisplayParams.ShowDescriptives;
                    }
                    else
                        ctl.Visible = false;
                else if (ctl is MM_OneLine_UnlinkedElement)
                    if (((ctl as MM_OneLine_UnlinkedElement).Tag as MM_OneLine_XmlComponent).Parent != null)
                        ctl.Visible = DisplayParams.ShowDescriptives;
                    else
                        ctl.Visible = DisplayParams.UnlinkedElements[((ctl as MM_OneLine_UnlinkedElement).Tag as MM_OneLine_XmlComponent).Name];
            }

            //Now, refresh the display
            olView.Refresh();
        }


        private void unlinkedComponentsToolStripMenuItem_Click(object sender, EventArgs e)
        {

            unlinkedComponentsToolStripMenuItem.Checked = (DisplayParams.ShowUnlinkedComponents ^= true);
            UpdateControls();
        }

        private void unlinkedToolStripMenuItem_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            (e.ClickedItem as ToolStripMenuItem).Checked = (DisplayParams.UnlinkedElements[e.ClickedItem.Text] ^= true);
            UpdateControls();
        }

        private void unlinkedToolStripMenuItem_Click(object sender, EventArgs e)
        {
            unlinkedToolStripMenuItem.Checked ^= true;
            foreach (ToolStripMenuItem tsI in unlinkedToolStripMenuItem.DropDownItems)
                tsI.Checked = DisplayParams.UnlinkedElements[tsI.Text] = unlinkedToolStripMenuItem.Checked;
            UpdateControls();
        }

        /// <summary>
        /// Roll up the descriptors to their parent elements
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void rollUpDescriptorsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (rollUpDescriptorsToolStripMenuItem.Checked)
                MessageBox.Show("Descriptors have already been rolled up!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            else
            {
                MM_DDL_Importer.RollUpDescriptors(olView, OLComponents);
            }
            rollUpDescriptorsToolStripMenuItem.Checked = true;
        }

        /// <summary>
        /// Remove all unlinked elements not associated with nodes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void removeUnlinkedElementsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (removeUnlinkedElementsToolStripMenuItem.Checked)
                MessageBox.Show("Unlinked elements have already been removed!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            else
            {
                List<MM_OneLine_Element> ToRemove = new List<MM_OneLine_Element>(olView.UnlinkedElements.Keys);
                List<MM_OneLine_Element> AssignedParams = new List<MM_OneLine_Element>();
                //Also remove all data-bound elements not properly associated for
                foreach (Control ctl in olView.PanelControls)
                    if (ctl is MM_OneLine_Element)
                    {
                        MM_OneLine_Element Elem = ctl as MM_OneLine_Element;
                        //If we find a node to remove, move the linkages to the new one
                        if (Elem.BaseElement != null && Elem is MM_OneLine_Node && olView.DisplayNodes.ContainsKey(Elem.BaseElement) && olView.DisplayNodes[Elem.BaseElement] != Elem)
                        {
                            ToRemove.Add(Elem);
                            MM_OneLine_Node Node = Elem as MM_OneLine_Node;
                            MM_OneLine_Node NewNode = olView.DisplayNodes[Elem.BaseElement];

                            foreach (KeyValuePair<MM_OneLine_Element, MM_OneLine_PokePoint[]> kvp in Node.ConnectionPoints)
                                if (!NewNode.ConnectionPoints.ContainsKey(kvp.Key))
                                {
                                    NewNode.ConnectionPoints.Add(kvp.Key, kvp.Value);
                                    NewNode.GraphicsPaths.Add(kvp.Key, Node.GraphicsPaths[kvp.Key]);
                                    Node.ConnectionPoints.Clear();
                                    Node.GraphicsPaths.Clear();
                                    if (String.IsNullOrEmpty(NewNode.ResourceNode))
                                        NewNode.ResourceNode = Node.ResourceNode;
                                }
                            AssignedParams.Add(NewNode);
                        }
                        else if (Elem.BaseElement != null && Elem is MM_OneLine_PokePoint == false && olView.DisplayElements.ContainsKey(Elem.BaseElement) && olView.DisplayElements[Elem.BaseElement] != Elem)
                        {
                            ToRemove.Add(Elem);
                            MM_OneLine_Element ReplacementElement = olView.DisplayElements[Elem.BaseElement];
                            foreach (MM_OneLine_Node Node in olView.DisplayNodes.Values)
                                if (Node.ConnectionPoints.ContainsKey(Elem))
                                {
                                    Node.ConnectionPoints.Add(ReplacementElement, Node.ConnectionPoints[Elem]);
                                    Node.GraphicsPaths.Add(ReplacementElement, Node.GraphicsPaths[Elem]);
                                    Node.ConnectionPoints.Remove(Elem);
                                    Node.GraphicsPaths.Remove(Elem);
                                }
                            AssignedParams.Add(ReplacementElement);
                            
                        }
                    }
                
                foreach (MM_OneLine_Element Elem in ToRemove)
                    if (Elem.Tag is MM_OneLine_XmlComponent)
                        OLComponents.Remove(Elem.Tag as MM_OneLine_XmlComponent);
                
                DisplayParams.HandleRemovedElements(ToRemove.ToArray());
                DisplayParams.HandleAddedElements(AssignedParams.ToArray());
                
                


                olView.Refresh();
                MM_Log.LogEvent(new MacomberMap.Common.Events.MM_Event_Information(String.Format("Removed {0:#,##0} unlinked elements", ToRemove.Count), typeof(MM_DDL_Importer)));
                removeUnlinkedElementsToolStripMenuItem.Checked = true;
            }
        }

        /// <summary>
        /// Handle the DDL import refresh menu option
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void refreshToolStripMenuItem_Click(object sender, EventArgs e)
        {
            validateTopologyToolStripMenuItem.Checked = approveOneLineToolStripMenuItem.Checked = matchElementsToolStripMenuItem.Checked = rollUpDescriptorsToolStripMenuItem.Checked = removeUnlinkedElementsToolStripMenuItem.Checked = rollUpNodesToolStripMenuItem.Checked = addPricingVectorsToolStripMenuItem.Checked = false;
            LoadOneLineDDL(LastDDL);
        }



        /// <summary>
        /// Connect all identified CIM elements with each other.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void rollUpNodesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (rollUpNodesToolStripMenuItem.Checked)
                MessageBox.Show("Nodes have already been rolled up!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            else
            {
                List<MM_OneLine_Node> Nodes = MM_DDL_Importer.RollUpNodes(Linkages, olView.PanelControls, tTip, DisplayParams, SubElement, OLComponents, olView);
                UpdateCIMElements();
                olView.Refresh();
                rollUpNodesToolStripMenuItem.Checked = true;
            }
        }
        #endregion

        #region Misc. API calls - Set ListView color
        /// <summary>
        /// This structure holds the GroupMetrics list
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        private struct LVGroupMetrics
        {
            public int cbSize;
            public int mask;
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
            public int crLeft;
            public int crTop;
            public int crRight;
            public int crBottom;
            public int crHeader;
            public int crFooter;
        }
        [DllImport("User32.dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, ref LVGroupMetrics lParam);


        /// <summary>
        /// Send a Win32 API call to the ListView control, in order to specify the color for the group.
        /// </summary>
        /// <param name="LVGroupColor"></param>
        private void SetListViewGroupColor(Color LVGroupColor)
        {
            LVGroupMetrics GroupMetrics = new LVGroupMetrics();
            GroupMetrics.cbSize = Marshal.SizeOf(GroupMetrics);
            GroupMetrics.mask = 4;
            GroupMetrics.crHeader = LVGroupColor.ToArgb();
            int Result = (int)SendMessage(lvElements.Handle, 4096 + 155, 0, ref GroupMetrics);
        }
        #endregion

        #region Mouse events
        /// <summary>
        /// Handle mouse movement in the one-line display by updating the coordinates
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void olView_MouseMove(object sender, MouseEventArgs e)
        {
            Point DecompiledPoint = new Point(e.X + olView.PanelScrollPosition.X, e.Y + olView.PanelScrollPosition.Y);
            foreach (Control ctl in olView.PanelControls)
                if (ctl.Bounds.Contains(DecompiledPoint))
                {
                    if (ctl.Tag is MM_OneLine_XmlComponent)
                        lblCoords.Text = e.X.ToString() + ", " + e.Y.ToString() + " (" + ctl.ToString() + "; " + ctl.Tag.ToString() + ")";
                    else
                        lblCoords.Text = e.X.ToString() + ", " + e.Y.ToString() + " (" + ctl.ToString() + ")";
                    return;
                }
            lblCoords.Text = e.X.ToString() + ", " + e.Y.ToString();

        }
        #endregion

        #region File Menu handling
        /// <summary>
        /// Save the one-line to XML
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void saveOneLineXMLToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (SaveFileDialog sFd = new SaveFileDialog())
            {
                sFd.Title = "Save MM One-Line";
                sFd.Filter = "MM One-Line XML (*.MM_OneLine)|*.MM_OneLine";
                sFd.FileName = SubElement.Name + ".MM_OneLine";
                if (sFd.ShowDialog() == DialogResult.OK)
                    using (XmlTextWriter xTw = new XmlTextWriter(sFd.FileName, Encoding.UTF8))
                    {
                        olView.WriteOneLineXml(xTw);
                        MessageBox.Show("One-line XML saved.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
            }

        }

        /// <summary>
        /// Save the one-line to DDL format
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void saveOneLineDDLToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrEmpty(RGB))
                using (OpenFileDialog oFd = new OpenFileDialog())
                {
                    oFd.Title = "Open the EMS RGB color specifications...";
                    oFd.Filter = "rgb.txt|rgb.txt";
                    if (oFd.ShowDialog() == DialogResult.OK)
                        RGB = oFd.FileName;
                    else
                        return;
                }

            using (SaveFileDialog sFd = new SaveFileDialog())
            {
                sFd.Title = "Save WebFG One-Line DDL";
                sFd.Filter = "WebFG DDL (*_dset.ddl)|*_dset.ddl";
                sFd.FileName = "Substation_" + SubElement.Name + "_dset.ddl";
                if (sFd.ShowDialog() == DialogResult.OK)
                    MM_DDL_Exporter.SaveDDL(RGB, olView, sFd.FileName, SubElement, new Point(-olView.AutoScrollPosition.X, -olView.AutoScrollPosition.Y), ESet);
            }
        }

        /// <summary>
        /// Save the one-line to JPEG format
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void saveOneLineJPEGToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (SaveFileDialog sFd = new SaveFileDialog())
            {
                sFd.Title = "Save MM One-Line JPEG image";
                sFd.Filter = "JPEG image (*.jpeg)|*.jpeg|Bitmap image (*.bmp)|*.bmp|PNG Image (*.png)|*.png";
                sFd.FileName = SubElement.Name + ".jpeg";
                if (sFd.ShowDialog() == DialogResult.OK)
                    olView.SaveImage(sFd.FileName);
            }
        }

        /// <summary>
        /// Clear the display of elements
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void newToolStripMenuItem_Click(object sender, EventArgs e)
        {
            olView.ClearElements();
            CIMControls.Clear();
            if (Elements != null)
                Elements.Clear();
            UpdateCIMElements();
            olView.Refresh();
            DisplayElements.Rows.Clear();

        }

        /// <summary>
        /// Handle the toggling of the information mode
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void informationModeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            informationModeToolStripMenuItem.Checked = olView.DisplayParameters.InformationMode = true;
            editorModeToolStripMenuItem.Checked = olView.DisplayParameters.EditorMode = false;
            olView.Refresh();
        }

        /// <summary>
        /// Handle the toggling of the editor mode
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void editorModeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            informationModeToolStripMenuItem.Checked = olView.DisplayParameters.InformationMode = false;
            editorModeToolStripMenuItem.Checked = olView.DisplayParameters.EditorMode = true;

            olView.Refresh();
        }


        /// <summary>
        /// Open a one-line XML file
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void openOneLineXMLToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog oFd = new OpenFileDialog())
            {
                oFd.Title = "Open MM One-Line";
                oFd.Filter = "MM One-Line XML (*.MM_OneLine)|*.MM_OneLine";
                if (oFd.ShowDialog() == DialogResult.OK)
                {
                    XmlDocument xDoc = new XmlDocument();
                    xDoc.Load(oFd.FileName);
                    olView.ClearElements();

                    //Wait until our CIM file is loaded
                    while (!MM_Database_CIM.CIMLoader.Completed)
                        Application.DoEvents();


                    olView.LoadXml(xDoc.DocumentElement);
                    AssignBaseElement(MM_DDL_Importer.SubElement = SubElement = MM_Database_CIM.TEIDs[olView.BaseElement.TEID]);
                    importToolStripMenuItem.Visible = true;
                    matchElementsToolStripMenuItem.Checked = rollUpDescriptorsToolStripMenuItem.Checked = removeUnlinkedElementsToolStripMenuItem.Checked = rollUpNodesToolStripMenuItem.Checked = addPricingVectorsToolStripMenuItem.Checked = false;
                }
            }
        }

        /// <summary>
        /// Print the one-line
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void printOneLineToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (PrintDialog pD = new PrintDialog())
            {
                pD.AllowCurrentPage = false;
                pD.AllowPrintToFile = true;
                pD.AllowSelection = false;
                pD.AllowSomePages = false;
                pD.ShowHelp = false;
                pD.UseEXDialog = true;
                pD.Document = new System.Drawing.Printing.PrintDocument();
                pD.Document.DocumentName = SubElement.Name + ".MM_OneLine";
                if (pD.ShowDialog() == DialogResult.OK)
                    olView.PrintImage(pD.Document);
            }

        }
        #endregion

        /// <summary>
        /// Handle double-clicking in the elements list view item, bringing up the CIM browser
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvElements_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            ListViewHitTestInfo hti = lvElements.HitTest(e.Location);
            if (hti.Item != null && hti.Item.Tag is CIM_Element)
                new frmCIMBrowser().Show(hti.Item.Tag as CIM_Element);
        }

        /// <summary>
        /// Handle the alinging of selected elements
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void alignToolStripMenuItem_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            olView.AlignSelection(e.ClickedItem.Text);
        }


        /// <summary>
        /// Handle double
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void olView_PanelDoubleClicked(object sender, MouseEventArgs e)
        {
            if (lvElements.SelectedItems.Count == 1 && (lvElements.SelectedItems[0] as CIMListViewItem).OLElement == null)
                DoDragDrop(new MM_Element[] { (lvElements.SelectedItems[0] as CIMListViewItem).MMElement }, DragDropEffects.All);
        }


        /// <summary>
        /// Handle the panel being clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void olView_PanelClicked(object sender, MouseEventArgs e)
        {
            //If we have a right-click, check to see whether the node lines are hit.
            if (e.Button == MouseButtons.Right)
            {
                List<MM_OneLine_Element> OutElems = new List<MM_OneLine_Element>();
                MM_OneLine_Node Node = null;
                using (Pen TestPen = new Pen(Color.DarkGreen, 3))
                    foreach (MM_OneLine_Node TestNode in olView.DisplayNodes.Values)
                        foreach (KeyValuePair<MM_OneLine_Element, GraphicsPath> kvp in TestNode.GraphicsPaths)
                            if (kvp.Value.IsOutlineVisible(e.Location, TestPen))
                            {
                                Node = TestNode;
                                OutElems.Add(kvp.Key);
                            }

                //Now, if we've found elements, handle accordingly
                if (OutElems.Count > 0)
                {
                    RightClickMenu.Items.Clear();
                    foreach (String NewStr in "&Add node poke point,Add &invisible poke point,Add &vertical jumper,Add &horizontal jumper".Split(','))
                    {
                        ToolStripMenuItem NewItem = new ToolStripMenuItem(NewStr);
                        NewItem.Tag = new Object[] { OutElems.ToArray(), Node, e.Location, olView.PanelScrollPosition };
                        NewItem.Click += MenuItem_Click;
                        RightClickMenu.Items.Add(NewItem);
                    }
                    RightClickMenu.Show(Cursor.Position);
                }
            }
        }

        /// <summary>
        /// Handle a menu item click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MenuItem_Click(object sender, EventArgs e)
        {
            ToolStripMenuItem Sender = (ToolStripMenuItem)sender;
            if (Sender.Text == "&View in CIM Browser")
                new frmCIMBrowser().Show(Sender.Tag as CIM_Element);
            else if (Sender.Tag is CIMListViewItem && Control.ModifierKeys == Keys.Control)
            {
                DoDragDrop(new MM_Element[] { (Sender.Tag as CIMListViewItem).MMElement }, DragDropEffects.All);
            }
            else if (Sender.Tag is CIMListViewItem)
            {
                lvElements.SelectedItems.Clear();
                (Sender.Tag as CIMListViewItem).EnsureVisible();
                (Sender.Tag as CIMListViewItem).Selected = true;
            }
            else if (Sender.Text == "&Flip transformer windings")
            {
                MM_OneLine_Transformer XF = Sender.Tag as MM_OneLine_Transformer;
                XF.Windings = new MM_OneLine_TransformerWinding[] { XF.Windings[1], XF.Windings[0] };
                XF.Orientation = XF.Orientation;
                olView.Refresh();
            }
            else if (Sender.Text == "&Re-check node connectivity")
            {
                if (Sender.Tag is MM_OneLine_PokePoint)
                    olView.CheckNodeConnections(Sender.Tag as MM_OneLine_PokePoint);
                else
                    MessageBox.Show("Unknown element type: " + Sender.Tag.GetType().Name);
            }
            else if (Sender.Text == "&Add node poke point" || Sender.Text == "Add &invisible poke point" || Sender.Text == "Add &vertical jumper" || Sender.Text == "Add &horizontal jumper")
            {
                object[] vals = Sender.Tag as Object[];
                MM_OneLine_Node BaseNode = vals[1] as MM_OneLine_Node;
                MM_OneLine_Element[] TargetElems = vals[0] as MM_OneLine_Element[];
                Point pt = (Point)vals[2];
                Point offset = (Point)vals[3];
                int HitRects = 0;

                foreach (MM_OneLine_Element TargetElem in TargetElems)
                {
                    List<MM_OneLine_Element> Rects = new List<MM_OneLine_Element>();
                    Rects.AddRange(BaseNode.ConnectionPoints[TargetElem]);
                    Rects.Add(TargetElem);

                    using (System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath())
                    using (Pen TestPen = new Pen(Color.DarkGreen, 3))
                    {
                        for (int a = 1; a < Rects.Count; a++)
                        {
                            gp.Reset();
                            MM_OneLine_Node.AddStraightLine(gp, olView.PanelScrollPosition, Rects[a - 1], Rects[a]);
                            if (gp.IsOutlineVisible(pt, TestPen))
                            {
                                bool IsVisible = Sender.Text != "Add &invisible poke point";
                                bool IsJumper = Sender.Text == "Add &vertical jumper" || Sender.Text == "Add &horizontal jumper";
                                int PokeSize = IsVisible ? 8 : 4;

                                MM_OneLine_PokePoint Poke = olView.LocatePoke(new Rectangle(pt.X - (PokeSize / 2), pt.Y - (PokeSize / 2), PokeSize, PokeSize), BaseNode, DisplayParams, BaseNode.BaseElement, MM_OneLine_Element.enumOrientations.Unknown, IsJumper, IsVisible);
                                if (IsJumper && Sender.Text == "Add &vertical jumper")
                                    Poke.Orientation = MM_OneLine_Element.enumOrientations.Vertical;
                                else if (IsJumper && Sender.Text == "Add &horizontal jumper")
                                    Poke.Orientation = MM_OneLine_Element.enumOrientations.Horizontal;

                                DisplayParams.HandleAddedElements(new MM_OneLine_Element[] { Poke });
                                MM_OneLine_PokePoint[] OutPoke = new MM_OneLine_PokePoint[BaseNode.ConnectionPoints[TargetElem].Length + 1];
                                for (int b = 0; b < OutPoke.Length; b++)
                                    if (b < a)
                                        OutPoke[b] = BaseNode.ConnectionPoints[TargetElem][b];
                                    else if (b == a)
                                        OutPoke[b] = Poke;
                                    else
                                        OutPoke[b] = BaseNode.ConnectionPoints[TargetElem][b - 1];
                                BaseNode.ConnectionPoints[TargetElem] = OutPoke;
                                BaseNode.RebuildPaths(offset);
                                HitRects++;
                            }
                        }
                    }
                    if (HitRects == 0)
                        MessageBox.Show("Unable to locate a path between node and element in which to " + Sender.Text.Replace("&", ""), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
                }

            }
        }


        /// <summary>
        /// Show the popup menu for a particular item
        /// </summary>
        /// <param name="Item">The list view item for the popup menu</param>
        /// <param name="Sender">The element associated with the popup menu</param>
        /// <param name="Location">The mouse location for popup</param>
        private void ShowPopupMenu(CIMListViewItem Item, MM_OneLine_Element Sender, Point Location)
        {
            //Build our menu
            RightClickMenu.Items.Clear();
            String OutString = Item.CIMElement.Type.Split(':')[1] + " ";

            if (Item.CIMElement.Type == "cim:ACLineSegment")
                OutString += Item.CIMElement.Parse("cim:Line").Name + (Item.CIMElement.Parse("cim:Line").InLinks.Length == 1 ? "" : Item.CIMElement.Name);
            else if (Item.CIMElement.Type == "cim:SeriesCompensator")
                OutString += Item.CIMElement.Parse("cim:Line").Name;
            else
                OutString += Item.CIMElement.Name;

            if (Item.CIMElement.VoltageLevel != null)
                OutString += " (" + Item.CIMElement.VoltageLevel.Name + ")";


            if (Item.CIMElement.Type == "cim:ACLineSegment" && Item.CIMElement.ACSegmentStations[0] == SubElement)
                RightClickMenu.Items.Add(OutString + " (to substation " + Item.CIMElement.ACSegmentStations[1].Name + ")");
            else if (Item.CIMElement.Type == "cim:ACLineSegment" && Item.CIMElement.ACSegmentStations[1] == SubElement)
                RightClickMenu.Items.Add(OutString + " (to substation " + Item.CIMElement.ACSegmentStations[0].Name + ")");
            else if (Item.CIMElement.Type == "cim:ACLineSegment")
                RightClickMenu.Items.Add(OutString + " (between substation " + Item.CIMElement.ACSegmentStations[0].Name + " and " + Item.CIMElement.ACSegmentStations[1].Name + ")");

            else
                RightClickMenu.Items.Add(OutString);









            //Add in our connected elements 
            if (Item.CIMElement.Elements.Length > 0)
            {
                RightClickMenu.Items.Add("-");
                RightClickMenu.Items.Add("Connected elements:");
                foreach (CIM_Element Elem in Item.CIMElement.Elements)
                    AddElementToMenu(Elem, Item.CIMElement);
            }

            //Add in our nodes, and keep track of elements on the other side
            List<CIM_Element> NodeElems = new List<CIM_Element>();
            if (Item.CIMElement.Nodes.Length > 0)
            {
                RightClickMenu.Items.Add("-");
                RightClickMenu.Items.Add("Connected nodes:");
                foreach (CIM_Element Node in Item.CIMElement.Nodes)
                {
                    AddElementToMenu(Node, Item.CIMElement);
                    foreach (CIM_Element Elem in Node.Elements)
                        if (Elem != Item.CIMElement && !NodeElems.Contains(Elem))
                            NodeElems.Add(Elem);
                }
            }

            //Add in our elements on the other side of the nodes
            if (NodeElems.Count > 0)
            {
                RightClickMenu.Items.Add("-");
                RightClickMenu.Items.Add("Elements connected to the element via nodes:");
                foreach (CIM_Element Elem in NodeElems)

                    AddElementToMenu(Elem, Item.CIMElement);
            }

            //Add in our menu options
            RightClickMenu.Items.Add("-");
            AddOptionToMenu(Item.CIMElement, "&View in CIM Browser");

            //If we have a transformer, offer to flip its windings
            if (Sender is MM_OneLine_Transformer)
                AddOptionToMenu(Sender, "&Flip transformer windings");

            //Locate a node, if we have it
            MM_OneLine_Node BaseNode = null;
            if (Sender is MM_OneLine_PokePoint && Sender is MM_OneLine_PricingVector == false)
                BaseNode = (Sender as MM_OneLine_PokePoint).BaseNode;

            //If we have a node, offer to recheck its connectivity and run a quick validation
            if (BaseNode != null)
            {
                AddOptionToMenu(Sender, "&Re-check node connectivity");
                //First, see if there are any elements referred into the node that doesn't exist
                foreach (MM_Element Elem in (BaseNode.BaseElement as MM_Node).ConnectedElements)
                    if (Elem is MM_PricingVector == false && olView.DisplayElements.ContainsKey(Elem) && !BaseNode.ConnectionPoints.ContainsKey(olView.DisplayElements[Elem]))
                        RightClickMenu.Items.Add(String.Format("Error: {0} {1} {2} is not connected to the node.", Elem.KVLevel.Name, Elem.ElemType.Name, Elem.Name));

                //Now, see if there are any incorrectly referred elements
                foreach (MM_OneLine_Element Elem in BaseNode.ConnectionPoints.Keys)
                    if (Elem is MM_OneLine_Transformer)
                    {
                        bool FoundWinding = false;
                        foreach (MM_OneLine_TransformerWinding Winding in (Elem as MM_OneLine_Transformer).Windings)
                            if (Winding != null && Array.IndexOf<MM_Element>((BaseNode.BaseElement as MM_Node).ConnectedElements, Winding.BaseElement) != -1)
                                FoundWinding = true;
                        if (!FoundWinding)
                            RightClickMenu.Items.Add("Error: Linkage from node " + BaseNode.BaseElement + " to transformer " + Elem.BaseElement.Name + " not found.");
                    }

                    else if (BaseNode.BaseElement != Elem.BaseElement && Array.IndexOf<MM_Element>((BaseNode.BaseElement as MM_Node).ConnectedElements, Elem.BaseElement) == -1)
                        RightClickMenu.Items.Add("Error: Linkage from node " + BaseNode.BaseElement + " to " + Elem.BaseElement.ElemType.Name + " " + Elem.BaseElement.Name + " not found.");
            }

            RightClickMenu.Show(Location);
        }

        /// <summary>
        /// Add an option to the menu bar, w/ associated tag for callup
        /// </summary>
        /// <param name="Tag">The tag for the menu</param>
        /// <param name="Text">The text of the menu</param>
        private void AddOptionToMenu(Object Tag, String Text)
        {
            ToolStripMenuItem NewItem = RightClickMenu.Items.Add(Text) as ToolStripMenuItem;
            NewItem.Tag = Tag;
            NewItem.Click += MenuItem_Click;
        }

        /// <summary>
        /// Add an element to the menu collection
        /// </summary>
        /// <param name="Element"></param>
        /// <param name="BaseElement"></param>
        private void AddElementToMenu(CIM_Element Element, CIM_Element BaseElement)
        {
            ToolStripMenuItem ts;
            String OutString = Element.Type.Split(':')[1] + " " + (Element.Type == "cim:ACLineSegment" ? Element.Parse("cim:Line").Name + Element.Name : Element.Name) + (Element.VoltageLevel == null ? "" : " (" + Element.VoltageLevel.Name + ")");

            if (Element.Type == "cim:ACLineSegment" && Element.ACSegmentStations[0] == SubElement)
                ts = RightClickMenu.Items.Add("  " + OutString + " (to substation " + Element.ACSegmentStations[1].Name + ")") as ToolStripMenuItem;
            else if (Element.Type == "cim:ACLineSegment" && Element.ACSegmentStations[1] == SubElement)
                ts = RightClickMenu.Items.Add("  " + OutString + " (to substation " + Element.ACSegmentStations[0].Name + ")") as ToolStripMenuItem;
            else if (Element.Type == "cim:ACLineSegment")
                ts = RightClickMenu.Items.Add("  " + OutString + " (between substation " + Element.ACSegmentStations[0].Name + " and " + Element.ACSegmentStations[1].Name + ")") as ToolStripMenuItem;
            else if (Element.Substation == null)
                ts = RightClickMenu.Items.Add("  " + OutString + " (substation unknown)") as ToolStripMenuItem;
            else if (Element.Substation != SubElement)
                ts = RightClickMenu.Items.Add("  " + OutString + " (in substation " + Element.Substation.Name + ")") as ToolStripMenuItem;
            else
                ts = RightClickMenu.Items.Add("  " + OutString) as ToolStripMenuItem;
            ts.Name = Element.TEID;

            //Now, link the element to the display
            MM_Element Elem2 = MM_Database_CIM.LocateElement(Element, olView.AddedElements);
            if (olView.DisplayElements.ContainsKey(Elem2))
                ts.ForeColor = Color.DarkGreen;
            if (lvElements.Items.ContainsKey(Elem2.TEID.ToString()))
            {
                ts.Tag = lvElements.Items[Elem2.TEID.ToString()];
                ts.Click += MenuItem_Click;
            }
            else
                ts.Enabled = false;

            //Now, add a transformer winding if needed
            if (Element.Type == "cim:TransformerWinding" && !RightClickMenu.Items.ContainsKey(Element.Parse("cim:PowerTransformer").TEID))
                AddElementToMenu(Element.Parse("cim:PowerTransformer"), BaseElement);
        }

        /// <summary>
        /// Handle the right-click on the menu.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvElements_MouseClick(object sender, MouseEventArgs e)
        {
            ListViewHitTestInfo ht = lvElements.HitTest(e.Location);
            if (ht.Item != null && (ht.Item as CIMListViewItem).CIMElement != null && e.Button == MouseButtons.Right)
                ShowPopupMenu(ht.Item as CIMListViewItem, null, lvElements.PointToScreen(e.Location));

        }






        /// <summary>
        /// Handle selection of items
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvElements_SelectedIndexChanged(object sender, EventArgs e)
        {
            if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
                return;
            List<MM_Element> Elem = new List<MM_Element>();
            foreach (CIMListViewItem lvI in lvElements.SelectedItems)
                Elem.Add(lvI.MMElement);
            olView.SelectElements(Elem.ToArray());

        }

        /// <summary>
        /// Handle the mouse movement outside of the selected range
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvElements_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && lvElements.DisplayRectangle.Contains(e.Location) && lvElements.Items.Count > 0)
            {
                List<MM_Element> Elems = new List<MM_Element>();
                foreach (CIMListViewItem lvI in lvElements.SelectedItems)
                    Elems.Add(lvI.MMElement);
                lvElements.DoDragDrop(Elems.ToArray(), DragDropEffects.Copy);
            }
        }

        /// <summary>
        /// Handle item dragging
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvElements_ItemDrag(object sender, ItemDragEventArgs e)
        {
            if (e.Button == MouseButtons.Left && e.Item is CIMListViewItem)
                lvElements.DoDragDrop(new MM_Element[] { (e.Item as CIMListViewItem).MMElement }, DragDropEffects.All);
        }

        /// <summary>
        /// Handle the selection change
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmbElements_SelectedIndexChanged(object sender, EventArgs e)
        {
            pgMain.SelectedObject = cmbElements.SelectedValue;
        }

        /// <summary>
        /// Go through all CIM elements in the list view, and color them appropriately.
        /// </summary>
        private void UpdateCIMElements()
        {
            foreach (CIMListViewItem lvI in lvElements.Items)
                lvI.UpdateState();


        }

        /// <summary>
        /// Run a topology validation against a one-line
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void validateTopologyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            int Errors, Warnings;
            String ValidationResults = MM_DDL_Importer.ValidateTopology(olView, SubElement, out Errors, out Warnings);
            Clipboard.Clear();
            Clipboard.SetText(ValidationResults);
            if (Errors > 0)
                MessageBox.Show(ValidationResults, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            else if (Warnings > 0)
                MessageBox.Show(ValidationResults, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
            else
                MessageBox.Show(ValidationResults, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
            if (Errors == 0 && Warnings == 0)
                validateTopologyToolStripMenuItem.Checked = true;

        }

        /// <summary>
        /// Handle a property change by refreshing the element
        /// </summary>
        /// <param name="s"></param>
        /// <param name="e"></param>
        private void pgMain_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
        {
            foreach (Object obj in (s as PropertyGrid).SelectedObjects)
                if (obj is MM_OneLine_Element)
                    olView.CheckNodeConnections(obj as MM_OneLine_Element);
            olView.Refresh();
        }

        /// <summary>
        /// Merge all poke points together
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mergePokePointsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //olView.MergePokePoints();
        }

        /// <summary>
        /// Approve the one-line by updating everything
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void approveOneLineToolStripMenuItem_Click(object sender, EventArgs e)
        {
            olView.PanelScrollPosition = Point.Empty;
            Application.DoEvents();
            olView.Refresh();

            //First, start by saving the Xml
            MemoryStream OutStream = new MemoryStream();
            XmlTextWriter xTw = new XmlTextWriter(OutStream, Encoding.UTF8);
            olView.WriteOneLineXml(xTw);
            String XmlText = Encoding.UTF8.GetString(OutStream.ToArray());

            //First, start by saving the Xml, loading it in a new window, and comparing it.
            if (SaveAndCompare(OutStream) != DialogResult.Yes)
                return;
            else
            {
                //If we have no directory, let's get one
                if (String.IsNullOrEmpty(AutoSaveDirectory))
                    using (FolderBrowserDialog fB = new FolderBrowserDialog())
                    {
                        fB.Description = "Select a path for approved one-line images and XML";
                        fB.ShowNewFolderButton = true;
                        if (fB.ShowDialog() != DialogResult.OK)
                            return;
                        AutoSaveDirectory = fB.SelectedPath;
                    }

                if (String.IsNullOrEmpty(AutoSaveDirectory))
                    return;


                //Write out the Xml to a file
                OutStream.Position = 0;
                using (FileStream WriteFile = File.Create(AutoSaveDirectory + "\\" + SubElement.Name + ".MM_OneLine"))
                    OutStream.WriteTo(WriteFile);

                //Write out the JPEG
                olView.OneLineImage.Save(AutoSaveDirectory + "\\" + SubElement.Name + ".jpeg", ImageFormat.Jpeg);

                //Save the Xml to the Oracle database
                RepositoryDb.SaveOneLine(MM_Database_CIM.LocateElement(SubElement, olView.AddedElements), XmlText);

                //Flag as ok
                approveOneLineToolStripMenuItem.Checked = true;

                MessageBox.Show("One-line for " + SubElement.Name + " saved.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        /// <summary>
        /// Save the current 
        /// </summary>
        /// <returns></returns>
        private DialogResult SaveAndCompare(MemoryStream OneLineStream)
        {
            StringBuilder CompareResults = new StringBuilder();
            CompareResults.AppendLine("Comparing original one-line and a loaded one-line saved from the original:");
            CompareResults.AppendLine();

            XmlDocument xDoc = new XmlDocument();
            OneLineStream.Position = 0;
            xDoc.Load(OneLineStream);
            OneLineStream.Position = 0;

            int Errors, Warnings;
            CompareResults.AppendLine(SaveAndCompareResults(xDoc, out Errors, out Warnings));
            CompareResults.AppendLine(String.Format("Errors: {0}, Warnings {1}", Errors, Warnings));
            CompareResults.AppendLine();
            CompareResults.Append("Would you like to continue?");
            if (Errors > 0)
                return MessageBox.Show(CompareResults.ToString(), Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Error);
            else if (Warnings > 0)
                return MessageBox.Show(CompareResults.ToString(), Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
            else
                return MessageBox.Show(CompareResults.ToString(), Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question);

        }

        private String SaveAndCompareResults(XmlDocument OneLineStream, out int Errors, out int Warnings)
        {
            Errors = Warnings = 0;
            StringBuilder CompareResults = new StringBuilder();
            using (MM_OneLine_Viewer Viewer = new MM_OneLine_Viewer())
            {
                //Viewer.DisplayParameters = DisplayParams;                
                //Viewer.LoadXml(xDoc.DocumentElement, null, null);

                try
                {
                    Viewer.AddedElements = olView.AddedElements;
                    Viewer.LoadXml(OneLineStream.DocumentElement);
                }
                catch (Exception ex)
                {
                    CompareResults.AppendLine("Error in loading XML: " + ex.Message);
                    Exception Inner = ex.InnerException;
                    while (Inner != null)
                    {
                        CompareResults.AppendLine("   " + Inner.Message);
                        Inner = Inner.InnerException;
                    }
                    CompareResults.AppendLine(ex.StackTrace);
                    CompareResults.AppendLine();
                    Errors++;
                    return CompareResults.ToString();
                }



                //Check the display elements
                foreach (KeyValuePair<MM_Element, MM_OneLine_Element> Disps in olView.DisplayElements)
                    if (!Viewer.DisplayElements.ContainsKey(Disps.Key))
                        CompareResults.AppendLine(String.Format("ERR {0:#,##0}: Loaded one-line missing {1} {2}.", ++Errors, Disps.Key.ElemType.Name, Disps.Key.Name));
                    else if (Disps.Value is MM_OneLine_Node == false)
                        CompareProperties(CompareResults, Disps.Value, Viewer.DisplayElements[Disps.Key], ref Errors, ref Warnings);
                foreach (KeyValuePair<MM_Element, MM_OneLine_Element> Disps in Viewer.DisplayElements)

                    if ((Disps.Value is MM_OneLine_PokePoint == false || Disps.Value is MM_OneLine_PricingVector) && !olView.DisplayElements.ContainsKey(Disps.Key))
                        CompareResults.AppendLine(String.Format("ERR {0:#,##0}: Loaded one-line has {1} {2}, which isn't in the original.", ++Errors, Disps.Key.ElemType.Name, Disps.Key.Name));

                //Check the display nodes
                foreach (KeyValuePair<MM_Element, MM_OneLine_Node> Node in olView.DisplayNodes)
                    if (!Viewer.DisplayNodes.ContainsKey(Node.Key))
                        CompareResults.AppendLine(String.Format("ERR {0:#,##0}: Loaded one-line missing {1} {2}.", ++Errors, Node.Key.ElemType.Name, Node.Key.Name));
                    else
                        CompareProperties(CompareResults, Node.Value, Viewer.DisplayNodes[Node.Key], ref Errors, ref Warnings);
                foreach (KeyValuePair<MM_Element, MM_OneLine_Node> Node in Viewer.DisplayNodes)
                    if (!olView.DisplayNodes.ContainsKey(Node.Key))
                        CompareResults.AppendLine(String.Format("ERR {0:#,##0}: Loaded one-line has {1} {2}, which isn't in the original.", ++Errors, Node.Key.ElemType.Name, Node.Key.Name));

                //Compare the descriptors
                foreach (KeyValuePair<MM_Element, MM_OneLine_Descriptor> Descriptor in olView.Descriptors)
                    if (!Viewer.Descriptors.ContainsKey(Descriptor.Key))
                        CompareResults.AppendLine(String.Format("ERR {0:#,##0}: Loaded one-line missing descriptor for {1} {2}.", ++Errors, Descriptor.Key.ElemType.Name, Descriptor.Key.Name));
                foreach (KeyValuePair<MM_Element, MM_OneLine_Descriptor> Descriptor in Viewer.Descriptors)
                    if (!olView.Descriptors.ContainsKey(Descriptor.Key) && olView.DisplayElements[Descriptor.Key].Descriptor == null)
                        CompareResults.AppendLine(String.Format("ERR {0:#,##0}: Loaded one-line has a descriptor for {1} {2}, which isn't in the original.", ++Errors, Descriptor.Key.ElemType.Name, Descriptor.Key.Name));

                //Compare the unlinked elements
                for (int a = 0; a < 2; a++)
                {
                    IEnumerable<MM_OneLine_Element> Elems = (a == 0 ? olView.UnlinkedElements.Keys : Viewer.UnlinkedElements.Keys);
                    IEnumerable<MM_OneLine_Element> Elems2 = (a == 1 ? olView.UnlinkedElements.Keys : Viewer.UnlinkedElements.Keys);

                    foreach (MM_OneLine_UnlinkedElement Elem in Elems)
                        if (Elem is MM_OneLine_UnlinkedElement)
                        {
                            bool FoundMatch = false;
                            foreach (MM_OneLine_Element Elem2 in Elems2)
                                if (!FoundMatch && Elem2 is MM_OneLine_UnlinkedElement && Elem.Bounds == Elem2.Bounds && Elem.Orientation == Elem2.Orientation && ((MM_OneLine_UnlinkedElement)Elem).ElemType == ((MM_OneLine_UnlinkedElement)Elem2).ElemType)
                                    FoundMatch = true;
                            if (!FoundMatch)
                                CompareResults.AppendLine(String.Format("ERR {0:#,##0}: {1} one-line lacks a(n) {2}-facing {3} at ({4},{5})-({6},{7})", ++Errors, a == 0 ? "Original" : "Loaded", Elem.Orientation, ((MM_OneLine_UnlinkedElement)Elem).ElemType, Elem.Left, Elem.Top, Elem.Right, Elem.Bottom));
                        }
                }
            }
            return CompareResults.ToString();
        }

        /// <summary>
        /// Compare the properties between two elements
        /// </summary>
        /// <param name="CompareResults">The outgoing results</param>
        /// <param name="Base">The first element</param>
        /// <param name="Other">The second element</param>
        /// <param name="Errors">The error count</param>
        /// <param name="Warnings">The warning count</param>
        private void CompareProperties(StringBuilder CompareResults, Object Base, Object Other, ref int Errors, ref int Warnings)
        {
            //First, go through all the properties for comparisons.

            foreach (PropertyInfo pI in Base.GetType().GetProperties())
                if (pI.CanWrite && pI.Name != "Descriptor" && pI.Name != "SecondaryDescriptor" && (pI.DeclaringType.Name.StartsWith("MM_") || pI.Name.Contains("Color") || pI.Name == "Font"))
                {
                    Object BaseVal = pI.GetValue(Base, null);
                    Object OtherVal = pI.GetValue(Other, null);
                    if (BaseVal == null && OtherVal == null)
                    { }
                    else if (BaseVal != null && OtherVal == null)
                        CompareResults.AppendLine(String.Format("ERR {0:#,##0}: Property {1} didn't import properly for {2}! Old value = {3}", ++Errors, pI.Name, Base.ToString(), BaseVal));
                    else if (pI.PropertyType == typeof(Font))
                    {
                        if (String.Compare(new FontConverter().ConvertToString(BaseVal), new FontConverter().ConvertToString(OtherVal), true) != 0)
                            CompareResults.AppendLine(String.Format("WRN {0:#,##0}: Font different between old and new: {1} = {2} vs. {3}={4}.", ++Warnings, Base.ToString(), new FontConverter().ConvertToString(BaseVal), Other.ToString(), new FontConverter().ConvertToString(OtherVal)));
                    }
                    else if (BaseVal.GetType().IsArray)
                    {
                        Array BaseArray = (Array)BaseVal;
                        Array OtherArray = (Array)OtherVal;
                        for (int a = 0; a < BaseArray.Length; a++)
                            CompareProperties(CompareResults, BaseArray.GetValue(a), OtherArray.GetValue(a), ref Errors, ref Warnings);
                    }
                    else if (!BaseVal.Equals(OtherVal))
                        CompareResults.AppendLine(String.Format("WRN {0:#,##0}: Property {1} different between old and new: {2}={3} vs. {4}={5}.", ++Warnings, pI.Name, Base, BaseVal, Other, OtherVal));
                }
        }

        /// <summary>
        /// Create samples of every element, and write them out
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void produceElementSamplesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (SaveFileDialog sFd = new SaveFileDialog())
            {
                sFd.FileName = "MM Element Collection.zip";
                sFd.Title = "Save the Macomber Map Element Images";
                sFd.Filter = "Compressed file (*.zip)|*.zip";
                if (sFd.ShowDialog() == DialogResult.OK)
                {
                    ToolStripProgressBar pb = new ToolStripProgressBar(sFd.FileName);
                    ssMain.Items.Add(pb);
                    ThreadPool.QueueUserWorkItem(new WaitCallback(ExportElements), new object[] { sFd.FileName, pb });
                }
            }
        }

        /// <summary>
        /// Safely update our export progress
        /// </summary>
        /// <param name="pb"></param>
        /// <param name="Current"></param>
        /// <param name="Total"></param>
        private delegate void SafeUpdateExportProgress(ToolStripProgressBar pb, int Current, int Total);


        /// <summary>
        /// Update the progress bar on our export
        /// </summary>
        /// <param name="pb"></param>
        /// <param name="Current"></param>
        /// <param name="Total"></param>
        private void UpdateExportProgress(ToolStripProgressBar pb, int Current, int Total)
        {
            if (pb.Owner.InvokeRequired)
                pb.Owner.BeginInvoke(new SafeUpdateExportProgress(UpdateExportProgress), pb, Current, Total);
            else
            {
                pb.Maximum = Total;
                pb.Value = Current;
                if (pb.Value == pb.Maximum)
                {
                    pb.Owner.Items.Remove(pb);
                    pb.Dispose();
                }
            }
        }


        /// <summary>
        /// Export the elements in its own thread
        /// </summary>
        /// <param name="state"></param>
        private void ExportElements(object state)
        {
            ToolStripProgressBar pb = (state as object[])[1] as ToolStripProgressBar;


            Size MaxSize = Size.Empty;
            SortedDictionary<String, Dictionary<MM_OneLine_Element.enumOrientations, MM_OneLine_Element>> Elements = new SortedDictionary<string, Dictionary<MM_OneLine_Element.enumOrientations, MM_OneLine_Element>>();
            MM_OneLine_Element.enumOrientations[] Orientations = (MM_OneLine_Element.enumOrientations[])Enum.GetValues(typeof(MM_OneLine_Element.enumOrientations));
            string[] ExportTypes = "Breaker:Opened=Checked,Breaker:Opened=Unchecked,Breaker:Opened=Indeterminate,Switch:Opened=Checked,Switch:Opened=Unchecked,Switch:Opened=Indeterminate,Unit:GenerationLevel=0%,Unit:GenerationLevel=25%,Unit:GenerationLevel=50%,Unit:GenerationLevel=75%,Unit:GenerationLevel=100%,PokePoint:IsJumper=true,PokePoint:IsVisible=true,PokePoint:IsVisible=false,PricingVector:IsPositive=true,PricingVector:IsPositive=false,Capacitor,LAAR,Line,Load,Reactor,Transformer".Split(',');
            MacomberMap.Common.Types.MM_KVLevel TempKV = new MacomberMap.Common.Types.MM_KVLevel("Temp", "Black");

            //Build our list of colors
            SortedDictionary<String, Color[]> Exports = new SortedDictionary<string, Color[]>();
            Exports.Add("BlackOnWhite", new Color[] { Color.Black, Color.White, Color.Black });
            Exports.Add("WhiteOnBlack", new Color[] { Color.White, Color.Black, Color.White });
            foreach (MM_KVLevel KVLevel in MM_Overall_Display.KVLevels.Values)
                Exports.Add(KVLevel.Name, new Color[] { KVLevel.Energized.ForeColor, Color.Black, Color.White });



            int Current = 0, Total = Exports.Count + ExportTypes.Length + (ExportTypes.Length * Orientations.Length * (Exports.Count + 1));

            UpdateExportProgress(pb, Current++, Total);
            foreach (String str in ExportTypes)
            {
                Elements.Add(str, new Dictionary<MM_OneLine_Element.enumOrientations, MM_OneLine_Element>());
                using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
                {
                    Size InSize = Size.Ceiling(g.MeasureString(str.Replace(':', '\n'), this.Font));
                    MaxSize.Width = Math.Max(MaxSize.Width, InSize.Width);
                    MaxSize.Height = Math.Max(MaxSize.Height, InSize.Height);
                }
                string[] splStr = str.Split(':');
                MM_Element TempElement = MM_Element.CreateElement(MM_Overall_Display.FindElementType(splStr[0]));
                TempElement.Name = "Test " + splStr[0];
                TempElement.KVLevel = TempKV;

                foreach (MM_OneLine_Element.enumOrientations Orientation in Orientations)
                {
                    UpdateExportProgress(pb, Current++, Total);
                    Type TargetType = MM_Type_Finder.LocateType("MM_OneLine_" + splStr[0], null);
                    MM_OneLine_Element NewElem = Activator.CreateInstance(TargetType, TempElement, DisplayParams, Orientation) as MM_OneLine_Element;
                    if (NewElem is MM_OneLine_Transformer && (Orientation == MM_OneLine_Element.enumOrientations.Up || Orientation == MM_OneLine_Element.enumOrientations.Down || Orientation == MM_OneLine_Element.enumOrientations.Vertical))
                        (NewElem as MM_OneLine_Transformer).Windings = new MM_OneLine_TransformerWinding[] {
                        new MM_OneLine_TransformerWinding(TempElement, new Rectangle(0,0, NewElem.Width, NewElem.Height/2), MM_OneLine_Element.enumOrientations.Up),
                        new MM_OneLine_TransformerWinding(TempElement, new Rectangle(0,(NewElem.Height/2)-1, NewElem.Width, NewElem.Height/2), MM_OneLine_Element.enumOrientations.Down)};
                    else if (NewElem is MM_OneLine_Transformer)
                        (NewElem as MM_OneLine_Transformer).Windings = new MM_OneLine_TransformerWinding[] {
                        new MM_OneLine_TransformerWinding(TempElement, new Rectangle(0,0, NewElem.Width/2, NewElem.Height), MM_OneLine_Element.enumOrientations.Right),
                        new MM_OneLine_TransformerWinding(TempElement, new Rectangle((NewElem.Width/2)-1, 0, NewElem.Width/2, NewElem.Height), MM_OneLine_Element.enumOrientations.Left)};
                    NewElem.BackColor = Color.White;
                    NewElem.ForeColor = TempElement.KVLevel.Energized.ForeColor;
                    if (splStr.Length > 1)
                        for (int a = 1; a < splStr.Length; a++)
                        {
                            string[] spl2 = splStr[a].Split('=');
                            PropertyInfo pI = NewElem.GetType().GetProperty(spl2[0]);
                            if (spl2[1].EndsWith("%"))
                                pI.SetValue(NewElem, float.Parse(spl2[1].Replace("%", "")) / 100f, null);
                            else
                                pI.SetValue(NewElem, MM_Serializable.RetrieveConvertedValue(pI.PropertyType, spl2[1]), null);
                        }

                    if (NewElem is MM_OneLine_PokePoint)
                        (NewElem as MM_OneLine_PokePoint).BaseNode = new MM_OneLine_Node(TempElement, DisplayParams, MM_OneLine_Element.enumOrientations.Unknown);
                    MaxSize.Width = Math.Max(NewElem.Width, MaxSize.Width);
                    MaxSize.Height = Math.Max(NewElem.Height, MaxSize.Height);
                    Elements[str].Add(Orientation, NewElem);
                }
            }

            //Make the size Symmetric
            MaxSize.Width = MaxSize.Height = Math.Max(MaxSize.Width, MaxSize.Height);

            //Create our ZIP stream
            using (ZipOutputStream OutZip = new ZipOutputStream(File.Create((state as object[])[0].ToString())))
            {
                //Set our compression level to high, and prepare to pull in the data
                OutZip.SetLevel(9);
                OutZip.SetComment("Macomber Map Schematic Elements Export on " + DateTime.Now.ToString());




                //Now that we have our elements, let's create our outgoing images
                foreach (KeyValuePair<String, Color[]> Export in Exports)
                    using (Bitmap OutImage = new Bitmap(MaxSize.Width * (Orientations.Length + 1), MaxSize.Height * (Elements.Count + 2)))
                    using (Graphics g = Graphics.FromImage(OutImage))
                    using (SolidBrush TextBrush = new SolidBrush(Export.Value[2]))
                    {
                        UpdateExportProgress(pb, Current++, Total);
                        g.Clear(Export.Value[1]);
                        TempKV.Energized.ForeColor = Export.Value[0];

                        //First, write out our header
                        using (Font TitleFont = new Font("Arial", 14, FontStyle.Bold))
                            g.DrawString(Export.Key, TitleFont, TextBrush, new Rectangle(0, 0, OutImage.Width - 1, 19), MM_OneLine_Element.CenterFormat);

                        for (int a = 0; a < Orientations.Length; a++)
                        {
                            Rectangle BoundRect = new Rectangle((MaxSize.Width * (a + 1)) + 1, 20, MaxSize.Width, MaxSize.Height);
                            g.DrawString(Orientations[a].ToString(), this.Font, TextBrush, BoundRect, MM_OneLine_Element.CenterFormat);
                        }
                        //Now, go through and write each element
                        int y = 20;
                        foreach (KeyValuePair<String, Dictionary<MM_OneLine_Element.enumOrientations, MM_OneLine_Element>> kvp in Elements)
                        {
                            y += MaxSize.Height + 1;
                            g.DrawString(kvp.Key.Replace(':', '\n'), this.Font, TextBrush, new Rectangle(0, y, MaxSize.Width, MaxSize.Height), MM_OneLine_Element.CenterFormat);
                            int x = MaxSize.Width + 1;
                            foreach (KeyValuePair<MM_OneLine_Element.enumOrientations, MM_OneLine_Element> Elem in kvp.Value)
                            {
                                Point CenterRect = MM_OneLine_Element.CenterRect(Elem.Value.Bounds);
                                Point CenterBox = MM_OneLine_Element.CenterRect(new Rectangle(Point.Empty, MaxSize));
                                Point TranslationPoint = new Point(CenterBox.X - CenterRect.X, CenterBox.Y - CenterRect.Y);
                                Elem.Value.BackColor = Export.Value[1];

                                if (Elem.Key == MM_OneLine_Element.enumOrientations.Up)
                                    g.DrawLine(TempKV.Energized.ForePen, x + CenterBox.X, y + CenterBox.Y, x + CenterBox.X, y + MaxSize.Height);
                                else if (Elem.Key == MM_OneLine_Element.enumOrientations.Down)
                                    g.DrawLine(TempKV.Energized.ForePen, x + CenterBox.X, y + CenterBox.Y, x + CenterBox.X, y);
                                else if (Elem.Key == MM_OneLine_Element.enumOrientations.Horizontal)
                                    g.DrawLine(TempKV.Energized.ForePen, x, y + CenterBox.Y, x + MaxSize.Width, y + CenterBox.Y);
                                else if (Elem.Key == MM_OneLine_Element.enumOrientations.Vertical)
                                    g.DrawLine(TempKV.Energized.ForePen, x + CenterBox.X, y, x + CenterBox.X, y + MaxSize.Height);
                                else if (Elem.Key == MM_OneLine_Element.enumOrientations.Left)
                                    g.DrawLine(TempKV.Energized.ForePen, x + CenterBox.X, y + CenterBox.Y, x + MaxSize.Width, y + CenterBox.Y);
                                else if (Elem.Key == MM_OneLine_Element.enumOrientations.Right)
                                    g.DrawLine(TempKV.Energized.ForePen, x + CenterBox.X, y + CenterBox.Y, x, y + CenterBox.Y);


                                g.DrawRectangle(Pens.LightGray, new Rectangle(x, y, MaxSize.Width, MaxSize.Height));


                                g.TranslateTransform(x + CenterBox.X - CenterRect.X, y + CenterBox.Y - CenterRect.Y);

                                Elem.Value.DrawImage(g);
                                g.ResetTransform();
                                x += MaxSize.Width + 1;
                            }
                        }

                        ZipEntry NewEntry = new ZipEntry(Export.Key + "\\MM Element Collection " + Export.Key + ".bmp");
                        NewEntry.DateTime = DateTime.Now;
                        NewEntry.Comment = "Macomber Map One Line Elements, exported on " + DateTime.Now.ToString();
                        OutZip.PutNextEntry(NewEntry);
                        using (MemoryStream mS = new MemoryStream())
                        {
                            OutImage.Save(mS, ImageFormat.Bmp);
                            OutZip.Write(mS.ToArray(), 0, (int)mS.Length);
                        }
                    }



                //Now, export the elements
                foreach (KeyValuePair<string, Dictionary<MM_OneLine_Element.enumOrientations, MM_OneLine_Element>> kvp in Elements)
                    foreach (KeyValuePair<MM_OneLine_Element.enumOrientations, MM_OneLine_Element> kvp2 in kvp.Value)
                        foreach (KeyValuePair<String, Color[]> Export in Exports)
                        {
                            UpdateExportProgress(pb, Current++, Total);
                            ZipEntry NewEntry = new ZipEntry(Export.Key + "\\" + kvp.Key.Replace(':', '\\') + "\\" + kvp.Key.Replace(':', ' ') + " " + kvp2.Key.ToString() + " " + Export.Key + ".bmp");

                            NewEntry.DateTime = DateTime.Now;
                            OutZip.PutNextEntry(NewEntry);
                            using (Bitmap bmp = new Bitmap(kvp2.Value.Width, kvp2.Value.Height))
                            using (Graphics g = Graphics.FromImage(bmp))
                            {
                                g.Clear(kvp2.Value.BackColor = Export.Value[1]);
                                kvp2.Value.ForeColor = TempKV.Energized.ForeColor = Export.Value[0];
                                kvp2.Value.DrawImage(g);
                                using (MemoryStream mS = new MemoryStream())
                                {
                                    bmp.Save(mS, ImageFormat.Bmp);
                                    OutZip.Write(mS.ToArray(), 0, (int)mS.Length);
                                }
                            }
                        }



                //Close out the zip file
                OutZip.Finish();
                OutZip.Close();

            }
            UpdateExportProgress(pb, Current, Current);
            Process.Start("MM Element Collection.zip");
        }

        /// <summary>
        /// Ask the user whether they'd like to quit
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void frmOneLineEditor_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing && MessageBox.Show("Are you sure you would like to close " + MM_Repository.TitleCase(Text) + "?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
                e.Cancel = true;
        }

        /// <summary>
        /// Update the zoomed-out panel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlZoomOut_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.DrawImage(olView.OneLineImage, pnlZoomOut.DisplayRectangle);
            float DivX = (float)pnlZoomOut.Width / (float)olView.PanelDisplaySize.Width;
            float DivY = (float)pnlZoomOut.Height / (float)olView.PanelDisplaySize.Height;
            e.Graphics.DrawRectangle(Pens.White, -olView.PanelScrollPosition.X * DivX, -olView.PanelScrollPosition.Y * DivY, olView.PanelSize.Width * DivX, olView.PanelSize.Height * DivY);





        }

        /// <summary>
        /// Handle mouse movement by zoom/panning the image
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlZoomOut_MouseMove(object sender, MouseEventArgs e)
        {
            float MultX = (float)olView.PanelDisplaySize.Width / (float)pnlZoomOut.Width;
            float MultY = (float)olView.PanelDisplaySize.Height / (float)pnlZoomOut.Height;
            Point CenterPoint = Point.Round(new PointF(e.X * MultX, e.Y * MultY));
            lblCoords.Text = String.Format("({0}, {1})", CenterPoint.X, CenterPoint.Y);

            if (e.Button == MouseButtons.Left)
            {


                olView.PanelScrollPosition = new Point((olView.PanelSize.Width / 2) - CenterPoint.X, (olView.PanelSize.Height / 2) - CenterPoint.Y);


                pnlZoomOut.Refresh();


            }
        }

        /// <summary>
        /// Handle mouse clicks in the zoom area
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlZoomOut_MouseDown(object sender, MouseEventArgs e)
        {
            pnlZoomOut_MouseMove(sender, e);
        }

        /// <summary>
        /// Use bulk one-line importing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void bulkOnelineImportToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //First, load in our ESET and colors
            if (String.IsNullOrEmpty(RGB))
                using (OpenFileDialog oFd = new OpenFileDialog())
                {
                    oFd.Title = "Open the EMS RGB color specifications...";
                    oFd.Filter = "rgb.txt|rgb.txt";
                    if (oFd.ShowDialog() == DialogResult.OK)
                        RGB = oFd.FileName;
                    else
                        return;
                }

            

            //Open the log file
            StreamWriter LogFile;
            using (SaveFileDialog sFd = new SaveFileDialog())
            {
                sFd.Title = "Select automated one-line import log file";
                sFd.Filter = "Log files (*.log)|*.log";
                if (sFd.ShowDialog() != DialogResult.OK)
                    return;
                LogFile = new StreamWriter(sFd.FileName, false, Encoding.UTF8);
                LogFile.WriteLine("ERCOT Macomber Map Loader, (C) 2010, Michael E. Legat, Ph.D.");
                LogFile.WriteLine();                
            }

            //If we have no directory, let's get one
            if (String.IsNullOrEmpty(AutoSaveDirectory))
                using (FolderBrowserDialog fB = new FolderBrowserDialog())
                {
                    fB.Description = "Select a path for approved one-line images and XML";
                    fB.ShowNewFolderButton = true;
                    if (fB.ShowDialog() != DialogResult.OK)
                        return;
                    AutoSaveDirectory = fB.SelectedPath;
                }

             //If we have no failed import directory, let's get one
            if (String.IsNullOrEmpty(FailedImportDirectory))
                using (FolderBrowserDialog fB = new FolderBrowserDialog())
                {
                    fB.Description = "Select a path for failed one-lines";
                    fB.ShowNewFolderButton = true;
                    if (fB.ShowDialog() != DialogResult.OK)
                        return;
                    FailedImportDirectory = fB.SelectedPath;
                }

            



            if (String.IsNullOrEmpty(AutoSaveDirectory))
                return;

            if (ESet == null)
                using (OpenFileDialog oFd = new OpenFileDialog())
                {
                    oFd.Title = "Open the ERCOT WebFG element set...";
                    oFd.Filter = "*_eset.ddl|*_eset.ddl";
                    if (oFd.ShowDialog() == DialogResult.OK)
                        ESet = MM_OneLine_Parser.ReadDDL(oFd.FileName);
                    else
                        return;
                }

            //Now, offer the open file dialog for everything          
            Dictionary<String, Point> Succesful = new Dictionary<string, Point>();
            Dictionary<String, Point> Unsuccesful = new Dictionary<string, Point>();
            Dictionary<String, StringBuilder> ErrorCollection = new Dictionary<string, StringBuilder>();
            List<String> FilesToProcess = new List<string>();
            if (MessageBox.Show("Would you like to process all the NETMOM one-lines within a directory?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            
                  using (FolderBrowserDialog fB = new FolderBrowserDialog())
                {
                    fB.Description = "Select a path for NETMOM one-lines";
                    fB.ShowNewFolderButton = true;
                    if (fB.ShowDialog() != DialogResult.OK)
                        return;
                    else 
                        foreach (FileInfo fI in new DirectoryInfo(fB.SelectedPath).GetFiles("znet_*_dset.ddl"))
                            FilesToProcess.Add(fI.FullName);
                  }  
                  
            
            else
                using (OpenFileDialog oFd = new OpenFileDialog())
            {
                oFd.Title = "Open ERCOT Substation one-lines for automated importer";
                oFd.Filter = "ZNET_*_dset.ddl|ZNET_*_dset.ddl";
                oFd.Multiselect = true;
                if (oFd.ShowDialog() != DialogResult.OK)
                    return;
                else
                    FilesToProcess.AddRange(oFd.FileNames);
                }
        

            //Now, go through the files one by one
            foreach (String File in FilesToProcess)
            {
                StringBuilder Results = new StringBuilder();
                int Errors = 0, Warnings = 0;
                try
                {
                    Results.AppendLine("Loading one-line DDL " + File);
                    LoadOneLineDDL(File);
                    Application.DoEvents();

                    Results.AppendLine("Matching elements");
                    matchElementsToolStripMenuItem_Click(matchElementsToolStripMenuItem, EventArgs.Empty);
                    Application.DoEvents();

                    Results.AppendLine("Rolling up descriptors");
                    rollUpDescriptorsToolStripMenuItem_Click(rollUpDescriptorsToolStripMenuItem, EventArgs.Empty);
                    Application.DoEvents();

                    Results.AppendLine("Rolling up nodes");
                    rollUpNodesToolStripMenuItem_Click(rollUpNodesToolStripMenuItem, EventArgs.Empty);
                    Application.DoEvents();

                    Results.AppendLine("Removing unlinked elements");
                    removeUnlinkedElementsToolStripMenuItem_Click(removeUnlinkedElementsToolStripMenuItem, EventArgs.Empty);
                    Application.DoEvents();

                    Results.AppendLine("Snapping to grid");
                    olView.AlignSelection("&Snap to grid");
                    Application.DoEvents();

                    Results.AppendLine("Adding pricing vectors");
                    addPricingVectorsToolStripMenuItem_Click(addPricingVectorsToolStripMenuItem, EventArgs.Empty);
                    Application.DoEvents();

                    //In the event of a one-line with more than one sub, remove unwanted elements.
                    Results.AppendLine("Removing elements from other substations");
                    List<MM_OneLine_Element> ElemToRemove = new List<MM_OneLine_Element>();
                    foreach (KeyValuePair<MM_Element, MM_OneLine_Element> kvp in olView.DisplayElements)
                        if (kvp.Key is MM_Line == false && kvp.Key.Substation != null && kvp.Key.Substation != olView.BaseElement)
                            ElemToRemove.Add(kvp.Value as MM_OneLine_Element);
                    DisplayParams.HandleRemovedElements(ElemToRemove.ToArray());
                    olView.Refresh();
                    Application.DoEvents();

                    //Now, run our auto-node checking, to automatically add in nodes of which all elements are connected
                    List<MM_OneLine_Node> AddedNodes = new List<MM_OneLine_Node>();
                    MM_OneLine_Node NodeElem;
                    foreach (CIM_Element Node in SubElement.Nodes)
                    {
                        MM_Node FoundNode = MM_Database_CIM.LocateElement(Node, olView.AddedElements) as MM_Node;
                        if (!olView.DisplayNodes.TryGetValue(FoundNode, out NodeElem))
                        //   NodeElem.Location = Point.Add(NodeElem.Location, new Size(4, 4));
                        // else                            
                        {
                            bool FoundElems = true;
                            Point TallyPoint = Point.Empty;
                            MM_OneLine_Element FoundElem;
                            foreach (MM_Element Elem in FoundNode.ConnectedElements)
                                if (olView.DisplayElements.TryGetValue(Elem, out FoundElem))
                                {
                                    Point CenterRect = MM_OneLine_Element.CenterRect(FoundElem.Bounds);
                                    TallyPoint.X += CenterRect.X;
                                    TallyPoint.Y += CenterRect.Y;
                                }
                                else
                                    FoundElems = false;

                            if (FoundElems)
                            {
                                MM_OneLine_Node NewNode = new MM_OneLine_Node(FoundNode, olView.DisplayParameters, MM_OneLine_Element.enumOrientations.Unknown);
                                NewNode.Bounds = new Rectangle((TallyPoint.X / FoundNode.ConnectedElements.Length) - 4, (TallyPoint.Y / FoundNode.ConnectedElements.Length) - 4, 8, 8);
                                AddedNodes.Add(NewNode);
                            }
                        }
                    }
                    if (AddedNodes.Count > 0)
                    {
                        DisplayParams.HandleAddedElements(AddedNodes.ToArray());
                        DisplayParams.HandleAddedNodes(AddedNodes.ToArray());
                    }

                    //Now, run validation                    
                    Results.AppendLine("Running validation");
                    String ValidationResults = MM_DDL_Importer.ValidateTopology(olView, SubElement, out Errors, out Warnings);
                    Application.DoEvents();
                    if (Errors > 0)
                        throw new Exception("Validation error: " + ValidationResults);

                    //Now, try and save things.
                    Results.AppendLine("Saving XML results");
                    XmlDocument xOneLine = new XmlDocument();
                    MemoryStream OutStream = new MemoryStream();
                    XmlTextWriter xTw = new XmlTextWriter(OutStream, Encoding.UTF8);
                    olView.WriteOneLineXml(xTw);
                    Application.DoEvents();
                    OutStream.Position = 0;
                    xOneLine.Load(OutStream);

                    String XmlText = Encoding.UTF8.GetString(OutStream.ToArray());
                    Results.AppendLine("Comparing XML results");
                    int Err2, Warn2;
                    String LoadAndSaveResults = SaveAndCompareResults(xOneLine, out Err2, out Warn2);
                    Errors += Err2;
                    Warnings += Warn2;

                    Application.DoEvents();
                    if (Errors > 0)
                        throw new Exception("Save/load error: " + ValidationResults);
                    Succesful.Add(SubElement.Name, new Point(Errors, Warnings));

                    Results.AppendLine("Saving one-line XML");
                    xOneLine.Save(AutoSaveDirectory + "\\" + SubElement.Name + ".MM_OneLine");
                    Application.DoEvents();

                    Results.AppendLine("Saving one-line image");
                    olView.SaveImage(AutoSaveDirectory + "\\" + SubElement.Name + ".jpeg");
                    Application.DoEvents();

                    //Results.AppendLine("Exporting WebFG One-Line");
                    //MM_DDL_Exporter.SaveDDL(RGB, olView, AutoSaveDirectory + "\\" + SubElement.Name + "_dset.ddl", SubElement, Point.Empty);

                    Application.DoEvents();
                }
                catch (Exception ex)
                {
                    Results.AppendLine(ex.Message);
                    Results.AppendLine("------- TRACE -------");
                    Results.AppendLine(ex.StackTrace);

                    int tab = 1;
                    Exception Inner = ex.InnerException;
                    while (Inner != null)
                    {
                        Results.AppendLine(new String('\t', tab) + Inner.Message);
                        Results.AppendLine(new String('\t', tab) + "------ INNER TRACE ----");
                        Results.AppendLine(new String('\t', tab) + Inner.StackTrace);
                        Inner = Inner.InnerException;
                        tab++;
                    }

                    if (Unsuccesful.ContainsKey(SubElement.Name))
                        Unsuccesful[SubElement.Name] = new Point(Unsuccesful[SubElement.Name].X + Errors, Unsuccesful[SubElement.Name].Y + Warnings);
                    else
                        Unsuccesful.Add(SubElement.Name, new Point(Errors, Warnings));
                    ErrorCollection.Add(Path.GetFileNameWithoutExtension(File), Results);

                    //Try to save it in the failure directory
                    try
                    {
                        System.IO.File.Copy(File, FailedImportDirectory + "\\" + Path.GetFileName(File));
                        using (XmlTextWriter xW = new XmlTextWriter(FailedImportDirectory + "\\" + SubElement.Name + ".MM_OneLine", Encoding.UTF8))
                            olView.WriteOneLineXml(xW);
                    }
                    catch (Exception)
                    { }
                }
            }
           

            LogFile.WriteLine("Succesful: " + Succesful.Count.ToString("#,##0"));
            foreach (KeyValuePair<String, Point> kvp in Succesful)
                LogFile.WriteLine("\t" + kvp.Key + " (Errors: " + kvp.Value.X + ", Warnings: " + kvp.Value.Y + ")");

            LogFile.WriteLine();
            LogFile.WriteLine("Unsuccesful: " + Unsuccesful.Count.ToString("#,##0"));
            foreach (KeyValuePair<String, Point> kvp in Unsuccesful)
                LogFile.WriteLine("\t" + kvp.Key + " (Errors: " + kvp.Value.X + ", Warnings: " + kvp.Value.Y + ")");
            
            LogFile.WriteLine();
            LogFile.WriteLine("Failed imports:");
            foreach (KeyValuePair<String, StringBuilder> kvp in ErrorCollection)
            {
                LogFile.WriteLine("Substation " + kvp.Key + ":");
                String InLine;
                using (StringReader sRd = new StringReader(kvp.Value.ToString()))
                    while ((InLine = sRd.ReadLine()) != null)
                        LogFile.WriteLine("\t" + InLine);
                LogFile.WriteLine();
                LogFile.WriteLine();
                LogFile.WriteLine();
            }
            LogFile.Close();
            MessageBox.Show("Bulk import complete. Succesful: " + Succesful.Count.ToString("#,##0") + ". Unsuccesful: " + Unsuccesful.Count.ToString("#,##0") + ".\nPlease see the log file for additional information.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        /// <summary>
        /// Create a breaker-to-breaker one-line
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void createBreakertobreakerOnelineToolStripMenuItem_Click(object sender, EventArgs e)
        {
            String Result = "";
            if (olView.BaseElement != null)
                Result = olView.BaseElement.TEID.ToString();
            if (InputBox(Application.ProductName, "Please enter the TEID of the seed element", ref Result) != DialogResult.OK || String.IsNullOrEmpty(Result))
                return;
            UInt64 TEID;
            CIM_Element SeedElement;
            if (!UInt64.TryParse(Result, out TEID))
                MessageBox.Show("Unable to parse TEID " + TEID, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            else if (!MM_Database_CIM.TEIDs.TryGetValue(TEID, out SeedElement))
                MessageBox.Show("Unable to locate seed element with TEID " + TEID, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            else
            {                
                Dictionary<CIM_Element, CIM_TraceList> TraceList = new Dictionary<CIM_Element, CIM_TraceList>();
                if (SeedElement.Type != "cim:Substation")
                    TraceList = CIM_Tracer.Tracer(SeedElement, false, true, "");
                else
                    foreach (CIM_Element Elem in SeedElement.Elements)
                        if (MM_Database_CIM.ReverseTEIDs.ContainsKey(Elem))
                            TraceList.Add(Elem, null);                
                CreateContingencyOneLine(SeedElement, TraceList);
            }
        }
       

        /// <summary>
        /// Create a contingency definition one-line
        /// </summary>
        /// <param name="SeedElement">The seed element</param>
        /// <param name="TraceList">The list of traced elements</param>
        public void CreateContingencyOneLine(CIM_Element SeedElement, Dictionary<CIM_Element, CIM_TraceList> TraceList)
        {            
            //Clear the display, and create our layout
            newToolStripMenuItem_Click(this, EventArgs.Empty);
            AssignBaseElement(SeedElement, new List<CIM_Element>(TraceList.Keys).ToArray());

            Dictionary<MM_Element, MM_OneLine_Element> NewElems = new Dictionary<MM_Element, MM_OneLine_Element>();
            List<MM_OneLine_Element> NewElemList = new List<MM_OneLine_Element>();
            List<MM_OneLine_Node> NewNodes = new List<MM_OneLine_Node>();
            //Set up our Tom Sawyer data                    
            TSLicenseManager.setUserName(System.Environment.UserName);
            TSLicenseManager.initTSSLicensing();
            TSDGraphManager graphManager = new TSDGraphManager();
            TSDGraph NewGraph = (TSDGraph)graphManager.addGraph();


            foreach (CIM_Element Elem in TraceList.Keys)
            {

                MM_Element FoundElem = MM_Database_CIM.LocateElement(Elem, olView.AddedElements);
                if (FoundElem != null && FoundElem.ElemType.Name != "BusbarSection" && FoundElem.ElemType.Name != "TransformerWinding" && FoundElem.ElemType.Name != "PricingVector")
                {

                    MM_OneLine_Element NewElem = MM_OneLine_Element.CreateElement(FoundElem, DisplayParams, olView.DisplayElements, MM_OneLine_Element.enumOrientations.Unknown);
                    if (NewElem != null)
                    {
                        NewElems.Add(FoundElem, NewElem);
                        NewElemList.Add(NewElem);

                        TSDNode NewNode = (TSDNode)NewGraph.addNode();
                        Rectangle NewRect = new Rectangle(NewElem.Left, NewElem.Top, Math.Max(NewElem.Width, NewElem.Height), Math.Max(NewElem.Width, NewElem.Height));
                        NewNode.setBounds(NewRect.Left, NewRect.Bottom, NewRect.Right, NewRect.Top);
                        NewNode.setTag(NewElem);


                        NewElem.Tag = NewNode;
                        if (NewElem is MM_OneLine_Node)
                        {
                            NewNodes.Add(NewElem as MM_OneLine_Node);
                            NewElem.Size = new Size(7, 7);
                            NewNode.setWidth(7);
                            NewNode.setHeight(7);

                            if (!String.IsNullOrEmpty((NewElem as MM_OneLine_Node).ResourceNode = Elem["etx:ElectricalBus>etx:ResourceNode>Name"]))
                            {
                                NewElem.SecondaryDescriptor = new MM_OneLine_SecondaryDescriptor(NewElem, DisplayParams);
                                NewElem.SecondaryDescriptor.RecomputeSize();
                                NewElemList.Add(NewElem.SecondaryDescriptor);
                                TSLabel NewLabel = NewNode.addLabel();
                                NewLabel.setText(NewElem.SecondaryDescriptor.ImageText);
                                NewLabel.setSize(NewElem.SecondaryDescriptor.Width, NewElem.SecondaryDescriptor.Height);
                            }
                        }
                        else
                        {
                            NewElem.Descriptor = new MM_OneLine_Descriptor(NewElem, DisplayParams);
                            NewElem.Descriptor.RecomputeSize();
                            NewElemList.Add(NewElem.Descriptor);
                            TSLabel NewLabel = NewNode.addLabel();
                            NewLabel.setText(NewElem.Descriptor.ImageText);
                            NewLabel.setSize(NewElem.Descriptor.Width, NewElem.Descriptor.Height);
                        }

                        if (NewElem is MM_OneLine_Transformer)
                        {
                            (NewElem as MM_OneLine_Transformer).Windings[0] = new MM_OneLine_TransformerWinding((NewElem.BaseElement as MM_Transformer).Winding1, new Rectangle(0, 0, NewElem.Width / 2, NewElem.Height), MM_OneLine_Element.enumOrientations.Left);
                            (NewElem as MM_OneLine_Transformer).Windings[1] = new MM_OneLine_TransformerWinding((NewElem.BaseElement as MM_Transformer).Winding2, new Rectangle(NewElem.Width / 2, 0, NewElem.Width / 2, NewElem.Height), MM_OneLine_Element.enumOrientations.Right);
                        }
                    }
                }
            }

            //Now, go through all nodes, and link them up
            foreach (MM_OneLine_Element NodeElem in NewElems.Values)
                if (NodeElem is MM_OneLine_Node)
                {
                    MM_Node BaseNode = NodeElem.BaseElement as MM_Node;
                    TSDNode BaseNodeTS = NodeElem.Tag as TSDNode;
                    foreach (MM_Element Elem in BaseNode.ConnectedElements)
                    {
                        if (NewElems.ContainsKey(Elem))
                            NewGraph.addEdge(BaseNodeTS, NewElems[Elem].Tag as TSDNode);
                        else if (Elem is MM_TransformerWinding && NewElems.ContainsKey((Elem as MM_TransformerWinding).Transformer))
                            NewGraph.addEdge(BaseNodeTS, NewElems[(Elem as MM_TransformerWinding).Transformer].Tag as TSDNode);
                    }
                }


            //Save our pre-run data
            TSLayoutXMLWriter xmlWriter = new com.tomsawyer.service.layout.xml.TSLayoutXMLWriter("C:\\MikeTest-PreRun.tsv");
            xmlWriter.setGraphManager(graphManager);
            xmlWriter.write();


            //Now, apply our layout
            TSLayoutProxy layoutProxy = new TSLayoutProxy();
            TSServiceInputData inputData = new TSServiceInputData();

            TSLayoutInputTailor layoutInputTailor = new TSLayoutInputTailor(inputData);
            layoutInputTailor.setGraphManager(graphManager);
            layoutInputTailor.setAsCurrentOperation();

            TSOrthogonalLayoutInputTailor orthoginalInputTailor = new TSOrthogonalLayoutInputTailor(inputData);
            orthoginalInputTailor.setGraph(graphManager.getMainDisplayGraph());
            orthoginalInputTailor.setAsCurrentLayoutStyle();

            //Go through and set up the size fixing and orientation of the components
            TSRoutingInputTailor routingInputTailor = new TSRoutingInputTailor(inputData);
            foreach (MM_OneLine_Element Elem in NewElems.Values)
                routingInputTailor.setFixedSize(Elem.Tag as TSDNode, Elem is MM_OneLine_Node == false);


            TSServiceOutputData outputData = new TSServiceOutputData();
            layoutProxy.run(inputData, outputData);
            TSApplyLayoutResults applyResults = new TSApplyLayoutResults();
            applyResults.apply(inputData, outputData);



            Point MinPoint = Point.Empty;
            foreach (MM_OneLine_Element Elem in NewElems.Values)
            {
                TSDNode Node = Elem.Tag as TSDNode;
                MinPoint.X = Math.Min(MinPoint.X, (int)Node.getLeft());
                MinPoint.Y = Math.Min(MinPoint.Y, (int)-Node.getBottom());
                if (!Node.labels().isEmpty())
                {
                    MinPoint.X = Math.Min(MinPoint.X, (int)(Node.labels().get(0) as TSLabel).getLeft());
                    MinPoint.Y = Math.Min(MinPoint.Y, (int)-(Node.labels().get(0) as TSLabel).getBottom());
                }
            }

            //Now, update all of our node positions
            foreach (MM_OneLine_Element NewElem in NewElems.Values)
            {
                TSDNode Node = NewElem.Tag as TSDNode;
                if (NewElem is MM_OneLine_Node)
                    NewElem.Size = new Size((int)Node.getWidth(), (int)Node.getHeight());

                StringBuilder Coords = new StringBuilder();
                Coords.AppendFormat("Node size: {0},{1}", Node.getWidth(), Node.getHeight());
                Coords.AppendFormat("  Elem size: {0},{1}", NewElem.Width, NewElem.Height);
                int DeltaX = (int)Math.Round((NewElem.Width - Node.getWidth()) / 2.0);
                int DeltaY = (int)Math.Round((NewElem.Height - Node.getHeight()) / 2.0);
                Coords.AppendFormat("  Delta: ({0},{1})", DeltaX, DeltaY);
                Coords.AppendFormat("  Old center: {0}, {1}", Node.getCenterX() - MinPoint.X, (int)-Node.getCenterY() + 60 - MinPoint.Y);
                NewElem.Center = new Point((int)Node.getCenterX() - MinPoint.X, (int)-Node.getCenterY() + 60 - MinPoint.Y);

                Coords.AppendFormat("  New center: {0}, {1}", NewElem.Center.X, NewElem.Center.Y);
                NewElem.Tag = Coords.ToString();
                if (NewElem.Descriptor != null)
                {
                    TSLabel NodeLabel = Node.labels().get(0) as TSLabel;
                    NewElem.Descriptor.Center = new Point((int)NodeLabel.getCenterX() - MinPoint.X, (int)-NodeLabel.getCenterY() + 60 - MinPoint.Y);
                }
                else if (NewElem.SecondaryDescriptor != null)
                {
                    TSLabel NodeLabel = Node.labels().get(0) as TSLabel;
                    NewElem.SecondaryDescriptor.Center = new Point((int)NodeLabel.getCenterX() - MinPoint.X, (int)-NodeLabel.getCenterY() + 60 - MinPoint.Y);
                }
            }

            //Build our node positioning
            java.util.Iterator edgeIterator = NewGraph.edges().iterator();
            while (edgeIterator.hasNext())
            {
                TSDEdge Edge = edgeIterator.next() as TSDEdge;
                MM_OneLine_Node Node = Edge.getSourceNode().getTag() as MM_OneLine_Node;
                MM_OneLine_Element TargetElement = Edge.getTargetNode().getTag() as MM_OneLine_Element;
                List<MM_OneLine_PokePoint> NewPokes = new List<MM_OneLine_PokePoint>();
                NewPokes.Add(Node);
                java.util.Iterator PointIter = Edge.bendPoints().iterator();
                while (PointIter.hasNext())
                {
                    com.tomsawyer.drawing.geometry.TSConstPoint NextPoint = (com.tomsawyer.drawing.geometry.TSConstPoint)PointIter.next();
                    MM_OneLine_PokePoint NewPoke = olView.LocatePoke(new Rectangle((int)NextPoint.getX() - MinPoint.X - 2, (int)-NextPoint.getY() + 58 - MinPoint.Y, 4, 4), Node, DisplayParams, Node.BaseElement, MM_OneLine_Element.enumOrientations.Unknown, false, false);
                    NewPokes.Add(NewPoke);
                    NewElemList.Add(NewPoke);
                }
                Node.ConnectionPoints.Add(TargetElement, NewPokes.ToArray());
            }

            //Write out our file as a TSV file for test purposes
            xmlWriter = new com.tomsawyer.service.layout.xml.TSLayoutXMLWriter("C:\\MikeTest-PostRun.tsv");
            xmlWriter.setGraphManager(graphManager);
            xmlWriter.write();



            //Update our orientations
            foreach (MM_OneLine_Element Elem in NewElemList)
                if (Elem is MM_OneLine_Node == false && Elem is MM_OneLine_PokePoint == false)
                {
                    Point CenterElem = MM_OneLine_Element.CenterRect(Elem.Bounds);
                    bool FoundPoint = false;
                    Point Min = Point.Empty;
                    Point Max = Point.Empty;
                    foreach (MM_OneLine_Node Node in NewNodes)
                        if (Node.ConnectionPoints.ContainsKey(Elem))
                        {
                            Point CenterRect = MM_OneLine_Element.CenterRect(Node.Bounds);
                            if (!FoundPoint)
                            {
                                FoundPoint = true;
                                Min = new Point(CenterRect.X, CenterRect.Y);
                                Max = new Point(CenterRect.X, CenterRect.Y);
                            }
                            else
                            {
                                Min.X = Math.Min(Min.X, CenterRect.X);
                                Min.Y = Math.Min(Min.Y, CenterRect.Y);
                                Max.X = Math.Max(Max.X, CenterRect.X);
                                Max.Y = Math.Max(Max.Y, CenterRect.Y);
                            }
                        }

                    MM_OneLine_Element.enumOrientations Orientation;
                    if (Max.X == Min.X && Max.Y == Min.Y)
                    {
                        int DeltaX = CenterElem.X - Min.X;
                        int DeltaY = CenterElem.Y - Min.Y;

                        if (Math.Abs(DeltaX) > Math.Abs(DeltaY))
                            Orientation = (DeltaX < 0 ? MM_OneLine_Element.enumOrientations.Left : MM_OneLine_Element.enumOrientations.Right);
                        else
                            Orientation = (DeltaX < 0 ? MM_OneLine_Element.enumOrientations.Up : MM_OneLine_Element.enumOrientations.Down);
                    }
                    else if ((Max.X - Min.X) > (Max.Y - Min.Y))
                        Orientation = MM_OneLine_Element.enumOrientations.Horizontal;
                    else
                        Orientation = MM_OneLine_Element.enumOrientations.Vertical;

                    Elem.Tag = Elem.Center;
                    Elem.Orientation = Orientation;
                }


            //Now, add in our elements
            DisplayParams.HandleAddedElements(NewElemList.ToArray());
            DisplayParams.HandleAddedNodes(NewNodes.ToArray());

            foreach (MM_OneLine_Element Elem in NewElemList)
                if (Elem.Tag is TSDNode)
                {
                    TSDNode ElemNode = Elem.Tag as TSDNode;
                    Elem.Center = new Point((int)Math.Round(ElemNode.getCenterX()), (int)Math.Round(ElemNode.getCenterY()));
                }
            this.Refresh();
        }

        /// <summary>
        /// Offer a simple input box, a-la VB
        /// </summary>
        /// <param name="title"></param>
        /// <param name="promptText"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static DialogResult InputBox(string title, string promptText, ref string value)
        {
            Form form = new Form();
            Label label = new Label();
            TextBox textBox = new TextBox();
            Button buttonOk = new Button();
            Button buttonCancel = new Button();

            form.Text = title;
            label.Text = promptText;
            textBox.Text = value;

            buttonOk.Text = "OK";
            buttonCancel.Text = "Cancel";
            buttonOk.DialogResult = DialogResult.OK;
            buttonCancel.DialogResult = DialogResult.Cancel;

            label.SetBounds(9, 20, 372, 13);
            textBox.SetBounds(12, 36, 372, 20);
            buttonOk.SetBounds(228, 72, 75, 23);
            buttonCancel.SetBounds(309, 72, 75, 23);

            label.AutoSize = true;
            textBox.Anchor = textBox.Anchor | AnchorStyles.Right;
            buttonOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
            buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;

            form.ClientSize = new Size(396, 107);
            form.Controls.AddRange(new Control[] { label, textBox, buttonOk, buttonCancel });
            form.ClientSize = new Size(Math.Max(300, label.Right + 10), form.ClientSize.Height);
            form.FormBorderStyle = FormBorderStyle.FixedDialog;
            form.StartPosition = FormStartPosition.CenterScreen;
            form.MinimizeBox = false;
            form.MaximizeBox = false;
            form.AcceptButton = buttonOk;
            form.CancelButton = buttonCancel;

            DialogResult dialogResult = form.ShowDialog();
            value = textBox.Text;
            return dialogResult;
        }

    }
}
